Merge branch 'develop' into 'master'

State resolution overhaul

See merge request famedly/conduit!53
merge-requests/56/head
Timo Kösters 3 years ago
commit d6b59cd20c

582
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -18,19 +18,20 @@ rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "93e62c86e
#rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] } #rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] }
# Used for matrix spec type definitions and helpers # Used for matrix spec type definitions and helpers
ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks", "unstable-exhaustive-types"], rev = "ee814aa84934530d76f5e4b275d739805b49bdef" } ruma = { git = "https://github.com/ruma/ruma", rev = "c1693569f15920e408aa6a26b7f3cc7fc6693a63", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
# ruma = { git = "https://github.com/DevinR528/ruma", features = ["rand", "client-api", "federation-api", "unstable-exhaustive-types", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "unstable-join" } #ruma = { git = "https://github.com/timokoesters/ruma", rev = "220d5b4a76b3b781f7f8297fbe6b14473b04214b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
# ruma = { path = "../ruma/ruma", features = ["unstable-exhaustive-types", "rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] } #ruma = { path = "../ruma/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "unstable-pre-spec", "unstable-exhaustive-types"] }
# Used when doing state resolution # Used when doing state resolution
# state-res = { git = "https://github.com/timokoesters/state-res", branch = "timo-spec-comp", features = ["unstable-pre-spec"] } state-res = { git = "https://github.com/timokoesters/state-res", rev = "9bb46ae681bfc361cff740e78dc42bb711db9779", features = ["unstable-pre-spec"] }
state-res = { git = "https://github.com/ruma/state-res", branch = "timo-spec-comp", features = ["unstable-pre-spec", "gen-eventid"] } #state-res = { path = "../state-res", features = ["unstable-pre-spec"] }
#state-res = { path = "../state-res", features = ["unstable-pre-spec", "gen-eventid"] }
# Used for long polling and federation sender, should be the same as rocket::tokio # Used for long polling and federation sender, should be the same as rocket::tokio
tokio = "1.2.0" tokio = "1.2.0"
# Used for storing data permanently # Used for storing data permanently
sled = { version = "0.34.6", features = ["no_metrics"] } sled = { version = "0.34.6", features = ["compression", "no_metrics"] }
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
# Used for emitting log entries # Used for emitting log entries
log = "0.4.14" log = "0.4.14"
# Used for rocket<->ruma conversions # Used for rocket<->ruma conversions
@ -48,7 +49,7 @@ rand = "0.8.3"
# Used to hash passwords # Used to hash passwords
rust-argon2 = "0.8.3" rust-argon2 = "0.8.3"
# Used to send requests # Used to send requests
reqwest = "0.11.1" reqwest = { version = "0.11.1" }
# Used for conduit::Error type # Used for conduit::Error type
thiserror = "1.0.24" thiserror = "1.0.24"
# Used to generate thumbnails for images # Used to generate thumbnails for images
@ -69,6 +70,7 @@ opentelemetry = "0.12.0"
tracing-subscriber = "0.2.16" tracing-subscriber = "0.2.16"
tracing-opentelemetry = "0.11.0" tracing-opentelemetry = "0.11.0"
opentelemetry-jaeger = "0.11.0" opentelemetry-jaeger = "0.11.0"
pretty_env_logger = "0.4.0"
[features] [features]
default = ["conduit_bin"] default = ["conduit_bin"]

@ -23,12 +23,11 @@ port = 6167
max_request_size = 20_000_000 # in bytes max_request_size = 20_000_000 # in bytes
# Disable registration. No new users will be able to register on this server # Disable registration. No new users will be able to register on this server
#allow_registration = true #allow_registration = false
# Disable encryption, so no new encrypted rooms can be created # Disable encryption, so no new encrypted rooms can be created
# Note: existing rooms will continue to work # Note: existing rooms will continue to work
#allow_encryption = true #allow_encryption = false
#allow_federation = false #allow_federation = false
# Enable jaeger to support monitoring and troubleshooting through jaeger # Enable jaeger to support monitoring and troubleshooting through jaeger
@ -36,6 +35,7 @@ max_request_size = 20_000_000 # in bytes
#cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024 #cache_capacity = 1073741824 # in bytes, 1024 * 1024 * 1024
#max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time #max_concurrent_requests = 4 # How many requests Conduit sends to other servers at the same time
#log = "info,state_res=warn,rocket=off,_=off,sled=off"
#workers = 4 # default: cpu core count * 2 #workers = 4 # default: cpu core count * 2
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy

@ -1 +1 @@
1.47.0 1.50.0

@ -1 +1,2 @@
merge_imports = true unstable_features = true
imports_granularity="Crate"

@ -1,7 +1,7 @@
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use http::header::{HeaderValue, CONTENT_TYPE}; use http::header::{HeaderValue, CONTENT_TYPE};
use log::warn; use log::warn;
use ruma::api::OutgoingRequest; use ruma::api::{IncomingResponse, OutgoingRequest};
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt::Debug, fmt::Debug,
@ -66,15 +66,10 @@ where
let status = reqwest_response.status(); let status = reqwest_response.status();
let body = reqwest_response let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
.bytes() warn!("server error: {}", e);
.await Vec::new().into()
.unwrap_or_else(|e| { }); // TODO: handle timeout
warn!("server error: {}", e);
Vec::new().into()
}) // TODO: handle timeout
.into_iter()
.collect::<Vec<_>>();
if status != 200 { if status != 200 {
warn!( warn!(
@ -86,7 +81,7 @@ where
); );
} }
let response = T::IncomingResponse::try_from( let response = T::IncomingResponse::try_from_http_response(
http_response http_response
.body(body) .body(body)
.expect("reqwest body is valid http body"), .expect("reqwest body is valid http body"),

@ -21,7 +21,7 @@ use ruma::{
}, },
EventType, EventType,
}, },
RoomAliasId, RoomId, RoomVersionId, UserId, push, RoomAliasId, RoomId, RoomVersionId, UserId,
}; };
use register::RegistrationKind; use register::RegistrationKind;
@ -181,7 +181,7 @@ pub async fn register_route(
EventType::PushRules, EventType::PushRules,
&ruma::events::push_rules::PushRulesEvent { &ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent { content: ruma::events::push_rules::PushRulesEventContent {
global: crate::push_rules::default_pushrules(&user_id), global: push::Ruleset::server_default(&user_id),
}, },
}, },
&db.globals, &db.globals,
@ -241,11 +241,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 2. Make conduit bot join // 2. Make conduit bot join
@ -266,11 +262,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 3. Power levels // 3. Power levels
@ -304,11 +296,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4.1 Join Rules // 4.1 Join Rules
@ -325,11 +313,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4.2 History Visibility // 4.2 History Visibility
@ -348,11 +332,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4.3 Guest Access // 4.3 Guest Access
@ -369,11 +349,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 6. Events implied by name and topic // 6. Events implied by name and topic
@ -392,11 +368,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.rooms.build_and_append_pdu( db.rooms.build_and_append_pdu(
@ -412,11 +384,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Room alias // Room alias
@ -438,11 +406,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?; db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
@ -465,11 +429,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.rooms.build_and_append_pdu( db.rooms.build_and_append_pdu(
PduBuilder { PduBuilder {
@ -488,27 +448,16 @@ pub async fn register_route(
}, },
&user_id, &user_id,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Send welcome message // Send welcome message
db.rooms.build_and_append_pdu( db.rooms.build_and_append_pdu(
PduBuilder { PduBuilder {
event_type: EventType::RoomMessage, event_type: EventType::RoomMessage,
content: serde_json::to_value(message::MessageEventContent::Text( content: serde_json::to_value(message::MessageEventContent::text_html(
message::TextMessageEventContent { "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing `/join #conduit:matrix.org`. **Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.** Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
body: "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing `/join #conduit:matrix.org`. **Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.** Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(), "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing <code>/join #conduit:matrix.org</code>. <strong>Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.</strong> Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
formatted: Some(message::FormattedBody {
format: message::MessageFormat::Html,
body: "Thanks for trying out Conduit! This software is still in development, so expect many bugs and missing features. If you have federation enabled, you can join the Conduit chat room by typing <code>/join #conduit:matrix.org</code>. <strong>Important: Please don't join any other Matrix rooms over federation without permission from the room's admins.</strong> Some actions might trigger bugs in other server implementations, breaking the chat for everyone else.".to_owned(),
}),
relates_to: None,
new_content: None,
},
)) ))
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
unsigned: None, unsigned: None,
@ -517,11 +466,7 @@ pub async fn register_route(
}, },
&conduit_user, &conduit_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -672,11 +617,11 @@ pub async fn deactivate_route(
} }
// Leave all joined rooms and reject all invitations // Leave all joined rooms and reject all invitations
for room_id in db for room_id in db.rooms.rooms_joined(&sender_user).chain(
.rooms db.rooms
.rooms_joined(&sender_user) .rooms_invited(&sender_user)
.chain(db.rooms.rooms_invited(&sender_user)) .map(|t| t.map(|(r, _)| r)),
{ ) {
let room_id = room_id?; let room_id = room_id?;
let event = member::MemberEventContent { let event = member::MemberEventContent {
membership: member::MembershipState::Leave, membership: member::MembershipState::Leave,
@ -696,11 +641,7 @@ pub async fn deactivate_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -716,3 +657,17 @@ pub async fn deactivate_route(
} }
.into()) .into())
} }
/*/
#[cfg_attr(
feature = "conduit_bin",
get("/_matrix/client/r0/account/3pid", data = "<body>")
)]
pub async fn third_party_route(
body: Ruma<account::add_3pid::Request<'_>>,
) -> ConduitResult<account::add_3pid::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(account::add_3pid::Response::default().into())
}
*/

@ -74,7 +74,7 @@ pub async fn get_alias_helper(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
room_alias.server_name().to_owned(), room_alias.server_name(),
federation::query::get_room_information::v1::Request { room_alias }, federation::query::get_room_information::v1::Request { room_alias },
) )
.await?; .await?;
@ -90,11 +90,23 @@ pub async fn get_alias_helper(
let aliases = registration let aliases = registration
.get("namespaces") .get("namespaces")
.and_then(|ns| ns.get("aliases")) .and_then(|ns| ns.get("aliases"))
.and_then(|users| users.get("regex")) .and_then(|aliases| aliases.as_sequence())
.and_then(|regex| regex.as_str()) .map_or_else(Vec::new, |aliases| {
.and_then(|regex| Regex::new(regex).ok()); aliases
.iter()
.map(|aliases| {
aliases
.get("regex")
.and_then(|regex| regex.as_str())
.and_then(|regex| Regex::new(regex).ok())
})
.filter_map(|o| o)
.collect::<Vec<_>>()
});
if aliases.map_or(false, |aliases| aliases.is_match(room_alias.as_str())) if aliases
.iter()
.any(|aliases| aliases.is_match(room_alias.as_str()))
&& db && db
.sending .sending
.send_appservice_request( .send_appservice_request(

@ -267,12 +267,10 @@ pub async fn get_backup_key_session_route(
let key_data = db let key_data = db
.key_backups .key_backups
.get_session(&sender_user, &body.version, &body.room_id, &body.session_id)? .get_session(&sender_user, &body.version, &body.room_id, &body.session_id)?
.ok_or_else(|| { .ok_or(Error::BadRequest(
Error::BadRequest( ErrorKind::NotFound,
ErrorKind::NotFound, "Backup key not found for this user's session.",
"Backup key not found for this user's session.", ))?;
)
})?;
Ok(get_backup_key_session::Response { key_data }.into()) Ok(get_backup_key_session::Response { key_data }.into())
} }

@ -1,5 +1,10 @@
use crate::ConduitResult; use crate::ConduitResult;
use ruma::{api::client::r0::capabilities::get_capabilities, RoomVersionId}; use ruma::{
api::client::r0::capabilities::{
get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability,
},
RoomVersionId,
};
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@ -12,24 +17,13 @@ use rocket::get;
#[tracing::instrument] #[tracing::instrument]
pub async fn get_capabilities_route() -> ConduitResult<get_capabilities::Response> { pub async fn get_capabilities_route() -> ConduitResult<get_capabilities::Response> {
let mut available = BTreeMap::new(); let mut available = BTreeMap::new();
available.insert( available.insert(RoomVersionId::Version6, RoomVersionStability::Stable);
RoomVersionId::Version5,
get_capabilities::RoomVersionStability::Stable,
);
available.insert(
RoomVersionId::Version6,
get_capabilities::RoomVersionStability::Stable,
);
Ok(get_capabilities::Response { let mut capabilities = Capabilities::new();
capabilities: get_capabilities::Capabilities { capabilities.room_versions = RoomVersionsCapability {
change_password: get_capabilities::ChangePasswordCapability::default(), // enabled by default default: RoomVersionId::Version6,
room_versions: get_capabilities::RoomVersionsCapability { available,
default: RoomVersionId::Version6, };
available,
}, Ok(get_capabilities::Response { capabilities }.into())
custom_capabilities: BTreeMap::new(),
},
}
.into())
} }

@ -3,7 +3,10 @@ use crate::{ConduitResult, Database, Error, Ruma};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
r0::config::{get_global_account_data, set_global_account_data}, r0::config::{
get_global_account_data, get_room_account_data, set_global_account_data,
set_room_account_data,
},
}, },
events::{custom::CustomEventContent, BasicEvent}, events::{custom::CustomEventContent, BasicEvent},
serde::Raw, serde::Raw,
@ -23,7 +26,7 @@ pub async fn set_global_account_data_route(
) -> ConduitResult<set_global_account_data::Response> { ) -> ConduitResult<set_global_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let content = serde_json::from_str::<serde_json::Value>(body.data.get()) let data = serde_json::from_str(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?; .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string(); let event_type = body.event_type.to_string();
@ -33,10 +36,7 @@ pub async fn set_global_account_data_route(
sender_user, sender_user,
event_type.clone().into(), event_type.clone().into(),
&BasicEvent { &BasicEvent {
content: CustomEventContent { content: CustomEventContent { event_type, data },
event_type,
json: content,
},
}, },
&db.globals, &db.globals,
)?; )?;
@ -46,6 +46,40 @@ pub async fn set_global_account_data_route(
Ok(set_global_account_data::Response.into()) Ok(set_global_account_data::Response.into())
} }
#[cfg_attr(
feature = "conduit_bin",
put(
"/_matrix/client/r0/user/<_>/rooms/<_>/account_data/<_>",
data = "<body>"
)
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_room_account_data_route(
db: State<'_, Database>,
body: Ruma<set_room_account_data::Request<'_>>,
) -> ConduitResult<set_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = serde_json::from_str(body.data.get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
db.account_data.update(
Some(&body.room_id),
sender_user,
event_type.clone().into(),
&BasicEvent {
content: CustomEventContent { event_type, data },
},
&db.globals,
)?;
db.flush().await?;
Ok(set_room_account_data::Response.into())
}
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>") get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
@ -66,3 +100,31 @@ pub async fn get_global_account_data_route(
Ok(get_global_account_data::Response { account_data: data }.into()) Ok(get_global_account_data::Response { account_data: data }.into())
} }
#[cfg_attr(
feature = "conduit_bin",
get(
"/_matrix/client/r0/user/<_>/rooms/<_>/account_data/<_>",
data = "<body>"
)
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_room_account_data_route(
db: State<'_, Database>,
body: Ruma<get_room_account_data::Request<'_>>,
) -> ConduitResult<get_room_account_data::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data = db
.account_data
.get::<Raw<ruma::events::AnyBasicEvent>>(
Some(&body.room_id),
sender_user,
body.event_type.clone().into(),
)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush().await?;
Ok(get_room_account_data::Response { account_data: data }.into())
}

@ -24,20 +24,25 @@ pub async fn get_context_route(
)); ));
} }
let base_pdu_id = db
.rooms
.get_pdu_id(&body.event_id)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"Base event id not found.",
))?;
let base_token = db.rooms.pdu_count(&base_pdu_id)?;
let base_event = db let base_event = db
.rooms .rooms
.get_pdu(&body.event_id)? .get_pdu_from_id(&base_pdu_id)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"Base event not found.", "Base event not found.",
))? ))?
.to_room_event(); .to_room_event();
let base_token = db
.rooms
.get_pdu_count(&body.event_id)?
.expect("event still exists");
let events_before = db let events_before = db
.rooms .rooms
.pdus_until(&sender_user, &body.room_id, base_token) .pdus_until(&sender_user, &body.room_id, base_token)

@ -141,7 +141,7 @@ pub async fn get_public_rooms_filtered_helper(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
other_server.to_owned(), other_server,
federation::directory::get_public_rooms_filtered::v1::Request { federation::directory::get_public_rooms_filtered::v1::Request {
limit, limit,
since: since.as_deref(), since: since.as_deref(),

@ -46,7 +46,7 @@ pub async fn create_content_route(
db.flush().await?; db.flush().await?;
Ok(create_content::Response { Ok(create_content::Response {
content_uri: mxc, content_uri: mxc.try_into().expect("Invalid mxc:// URI"),
blurhash: None, blurhash: None,
} }
.into()) .into())
@ -80,7 +80,7 @@ pub async fn get_content_route(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
body.server_name.clone(), &body.server_name,
get_content::Request { get_content::Request {
allow_remote: false, allow_remote: false,
server_name: &body.server_name, server_name: &body.server_name,
@ -130,12 +130,12 @@ pub async fn get_content_thumbnail_route(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
body.server_name.clone(), &body.server_name,
get_content_thumbnail::Request { get_content_thumbnail::Request {
allow_remote: false, allow_remote: false,
height: body.height, height: body.height,
width: body.width, width: body.width,
method: body.method, method: body.method.clone(),
server_name: &body.server_name, server_name: &body.server_name,
media_id: &body.media_id, media_id: &body.media_id,
}, },

@ -2,9 +2,10 @@ use super::State;
use crate::{ use crate::{
client_server, client_server,
pdu::{PduBuilder, PduEvent}, pdu::{PduBuilder, PduEvent},
utils, ConduitResult, Database, Error, Result, Ruma, server_server, utils, ConduitResult, Database, Error, Result, Ruma,
}; };
use log::warn; use log::{error, warn};
use rocket::futures;
use ruma::{ use ruma::{
api::{ api::{
client::{ client::{
@ -21,13 +22,7 @@ use ruma::{
serde::{to_canonical_value, CanonicalJsonObject, Raw}, serde::{to_canonical_value, CanonicalJsonObject, Raw},
EventId, RoomId, RoomVersionId, ServerName, UserId, EventId, RoomId, RoomVersionId, ServerName, UserId,
}; };
use state_res::StateEvent; use std::{collections::BTreeMap, convert::TryFrom, sync::RwLock};
use std::{
collections::{BTreeMap, HashMap, HashSet},
convert::TryFrom,
iter,
sync::Arc,
};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; use rocket::{get, post};
@ -97,42 +92,7 @@ pub async fn leave_room_route(
) -> ConduitResult<leave_room::Response> { ) -> ConduitResult<leave_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>( db.rooms.leave_room(sender_user, &body.room_id, &db).await?;
db.rooms
.room_state_get(
&body.room_id,
&EventType::RoomMember,
&sender_user.to_string(),
)?
.ok_or(Error::BadRequest(
ErrorKind::BadState,
"Cannot leave a room you are not a member of.",
))?
.1
.content,
)
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
event.membership = member::MembershipState::Leave;
db.rooms.build_and_append_pdu(
PduBuilder {
event_type: EventType::RoomMember,
content: serde_json::to_value(event).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(sender_user.to_string()),
redacts: None,
},
&sender_user,
&body.room_id,
&db.globals,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?;
db.flush().await?; db.flush().await?;
@ -168,11 +128,7 @@ pub async fn invite_user_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;
@ -205,7 +161,6 @@ pub async fn kick_user_route(
ErrorKind::BadState, ErrorKind::BadState,
"Cannot kick member that's not in the room.", "Cannot kick member that's not in the room.",
))? ))?
.1
.content, .content,
) )
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
@ -225,11 +180,7 @@ pub async fn kick_user_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;
@ -265,7 +216,7 @@ pub async fn ban_user_route(
is_direct: None, is_direct: None,
third_party_invite: None, third_party_invite: None,
}), }),
|(_, event)| { |event| {
let mut event = let mut event =
serde_json::from_value::<Raw<member::MemberEventContent>>(event.content) serde_json::from_value::<Raw<member::MemberEventContent>>(event.content)
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
@ -286,11 +237,7 @@ pub async fn ban_user_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;
@ -320,7 +267,6 @@ pub async fn unban_user_route(
ErrorKind::BadState, ErrorKind::BadState,
"Cannot unban a user who is not banned.", "Cannot unban a user who is not banned.",
))? ))?
.1
.content, .content,
) )
.expect("from_value::<Raw<..>> can never fail") .expect("from_value::<Raw<..>> can never fail")
@ -339,11 +285,7 @@ pub async fn unban_user_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;
@ -459,6 +401,7 @@ pub async fn joined_members_route(
Ok(joined_members::Response { joined }.into()) Ok(joined_members::Response { joined }.into())
} }
#[tracing::instrument(skip(db))]
async fn join_room_by_id_helper( async fn join_room_by_id_helper(
db: &Database, db: &Database,
sender_user: Option<&UserId>, sender_user: Option<&UserId>,
@ -479,11 +422,11 @@ async fn join_room_by_id_helper(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
remote_server.clone(), remote_server,
federation::membership::create_join_event_template::v1::Request { federation::membership::create_join_event_template::v1::Request {
room_id, room_id,
user_id: sender_user, user_id: sender_user,
ver: &[RoomVersionId::Version5, RoomVersionId::Version6], ver: &[RoomVersionId::Version6],
}, },
) )
.await; .await;
@ -497,12 +440,18 @@ async fn join_room_by_id_helper(
let (make_join_response, remote_server) = make_join_response_and_server?; 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::Version6 => room_version,
_ => return Err(Error::BadServerResponse("Room version is not supported")),
};
let mut join_event_stub = let mut join_event_stub =
serde_json::from_str::<CanonicalJsonObject>(make_join_response.event.json().get()) serde_json::from_str::<CanonicalJsonObject>(make_join_response.event.json().get())
.map_err(|_| { .map_err(|_| {
Error::BadServerResponse("Invalid make_join event json received from server.") Error::BadServerResponse("Invalid make_join event json received from server.")
})?; })?;
// TODO: Is origin needed?
join_event_stub.insert( join_event_stub.insert(
"origin".to_owned(), "origin".to_owned(),
to_canonical_value(db.globals.server_name()) to_canonical_value(db.globals.server_name())
@ -533,14 +482,14 @@ async fn join_room_by_id_helper(
db.globals.server_name().as_str(), db.globals.server_name().as_str(),
db.globals.keypair(), db.globals.keypair(),
&mut join_event_stub, &mut join_event_stub,
&RoomVersionId::Version6, &room_version,
) )
.expect("event is valid, we just created it"); .expect("event is valid, we just created it");
// Generate event id // Generate event id
let event_id = EventId::try_from(&*format!( let event_id = EventId::try_from(&*format!(
"${}", "${}",
ruma::signatures::reference_hash(&join_event_stub, &RoomVersionId::Version6) ruma::signatures::reference_hash(&join_event_stub, &room_version)
.expect("ruma can calculate reference hashes") .expect("ruma can calculate reference hashes")
)) ))
.expect("ruma's reference hashes are valid event ids"); .expect("ruma's reference hashes are valid event ids");
@ -558,7 +507,7 @@ async fn join_room_by_id_helper(
.sending .sending
.send_federation_request( .send_federation_request(
&db.globals, &db.globals,
remote_server.clone(), remote_server,
federation::membership::create_join_event::v2::Request { federation::membership::create_join_event::v2::Request {
room_id, room_id,
event_id: &event_id, event_id: &event_id,
@ -567,150 +516,120 @@ async fn join_room_by_id_helper(
) )
.await?; .await?;
let add_event_id = |pdu: &Raw<Pdu>| -> Result<(EventId, CanonicalJsonObject)> { let count = db.globals.next_count()?;
let mut value = serde_json::from_str(pdu.json().get())
.expect("converting raw jsons to values always works");
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &RoomVersionId::Version6)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
value.insert(
"event_id".to_owned(),
to_canonical_value(&event_id)
.expect("a valid EventId can be converted to CanonicalJsonValue"),
);
Ok((event_id, value))
};
let room_state = send_join_response.room_state.state.iter().map(add_event_id);
let state_events = room_state let mut pdu_id = room_id.as_bytes().to_vec();
.clone() pdu_id.push(0xff);
.map(|pdu: Result<(EventId, CanonicalJsonObject)>| Ok(pdu?.0)) pdu_id.extend_from_slice(&count.to_be_bytes());
.chain(iter::once(Ok(event_id.clone()))) // Add join event we just created
.collect::<Result<HashSet<EventId>>>()?;
let auth_chain = send_join_response let pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.room_state .map_err(|_| Error::BadServerResponse("Invalid PDU in send_join response."))?;
.auth_chain
.iter()
.map(add_event_id);
let mut event_map = room_state
.chain(auth_chain)
.chain(iter::once(Ok((event_id, join_event)))) // Add join event we just created
.map(|r| {
let (event_id, value) = r?;
state_res::StateEvent::from_id_canon_obj(event_id.clone(), value.clone())
.map(|ev| (event_id, Arc::new(ev)))
.map_err(|e| {
warn!("{:?}: {}", value, e);
Error::BadServerResponse("Invalid PDU in send_join response.")
})
})
.collect::<Result<BTreeMap<EventId, Arc<StateEvent>>>>()?;
let control_events = event_map let mut state = BTreeMap::new();
.values() let pub_key_map = RwLock::new(BTreeMap::new());
.filter(|pdu| pdu.is_power_event())
.map(|pdu| pdu.event_id())
.collect::<Vec<_>>();
// These events are not guaranteed to be sorted but they are resolved according to spec for result in futures::future::join_all(
// we auth them anyways to weed out faulty/malicious server. The following is basically the send_join_response
// full state resolution algorithm. .room_state
let event_ids = event_map.keys().cloned().collect::<Vec<_>>(); .state
.iter()
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map, &db)),
)
.await
{
let (event_id, value) = match result {
Ok(t) => t,
Err(_) => continue,
};
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
warn!("{:?}: {}", value, e);
Error::BadServerResponse("Invalid PDU in send_join response.")
})?;
db.rooms.add_pdu_outlier(&pdu)?;
if let Some(state_key) = &pdu.state_key {
if pdu.kind == EventType::RoomMember {
let target_user_id = UserId::try_from(state_key.clone()).map_err(|e| {
warn!(
"Invalid user id in send_join response: {}: {}",
state_key, e
);
Error::BadServerResponse("Invalid user id in send_join response.")
})?;
let invite_state = Vec::new(); // TODO add a few important events
// Update our membership info, we do this here incase a user is invited
// and immediately leaves we need the DB to record the invite event for auth
db.rooms.update_membership(
&pdu.room_id,
&target_user_id,
serde_json::from_value::<member::MembershipState>(
pdu.content
.get("membership")
.ok_or(Error::BadServerResponse("Invalid member event content"))?
.clone(),
)
.map_err(|_| {
Error::BadServerResponse("Invalid membership state content.")
})?,
&pdu.sender,
Some(invite_state),
db,
)?;
}
state.insert((pdu.kind.clone(), state_key.clone()), pdu.event_id.clone());
}
}
let sorted_control_events = state_res::StateResolution::reverse_topological_power_sort( state.insert(
&room_id, (
&control_events, pdu.kind.clone(),
&mut event_map, pdu.state_key.clone().expect("join event has state key"),
&db.rooms, ),
&event_ids, pdu.event_id.clone(),
); );
// Auth check each event against the "partial" state created by the preceding events db.rooms.force_state(room_id, state, &db.globals)?;
let resolved_control_events = state_res::StateResolution::iterative_auth_check(
room_id,
&RoomVersionId::Version6,
&sorted_control_events,
&BTreeMap::new(), // We have no "clean/resolved" events to add (these extend the `resolved_control_events`)
&mut event_map,
&db.rooms,
)
.expect("iterative auth check failed on resolved events");
// This removes the control events that failed auth, leaving the resolved
// to be mainline sorted. In the actual `state_res::StateResolution::resolve`
// function both are removed since these are all events we don't know of
// we must keep track of everything to add to our DB.
let events_to_sort = event_map
.keys()
.filter(|id| {
!sorted_control_events.contains(id)
|| resolved_control_events.values().any(|rid| *id == rid)
})
.cloned()
.collect::<Vec<_>>();
let power_level = resolved_control_events.get(&(EventType::RoomPowerLevels, "".into()));
// Sort the remaining non control events
let sorted_event_ids = state_res::StateResolution::mainline_sort(
room_id,
&events_to_sort,
power_level,
&mut event_map,
&db.rooms,
);
let resolved_events = state_res::StateResolution::iterative_auth_check( for result in futures::future::join_all(
room_id, send_join_response
&RoomVersionId::Version6, .room_state
&sorted_event_ids, .auth_chain
&resolved_control_events, .iter()
&mut event_map, .map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map, &db)),
&db.rooms,
) )
.expect("iterative auth check failed on resolved events"); .await
let mut state = HashMap::new();
// filter the events that failed the auth check keeping the remaining events
// sorted correctly
for ev_id in sorted_event_ids
.iter()
.filter(|id| resolved_events.values().any(|rid| rid == *id))
{ {
// this is a `state_res::StateEvent` that holds a `ruma::Pdu` let (event_id, value) = match result {
let pdu = event_map Ok(t) => t,
.get(ev_id) Err(_) => continue,
.expect("Found event_id in sorted events that is not in resolved state"); };
// We do not rebuild the PDU in this case only insert to DB let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
let count = db.globals.next_count()?; warn!("{:?}: {}", value, e);
let mut pdu_id = room_id.as_bytes().to_vec(); Error::BadServerResponse("Invalid PDU in send_join response.")
pdu_id.push(0xff); })?;
pdu_id.extend_from_slice(&count.to_be_bytes()); db.rooms.add_pdu_outlier(&pdu)?;
db.rooms.append_pdu(
&PduEvent::from(&**pdu),
utils::to_canonical_object(&**pdu).expect("Pdu is valid canonical object"),
count,
pdu_id.clone().into(),
&db.globals,
&db.account_data,
&db.admin,
)?;
if state_events.contains(ev_id) {
state.insert((pdu.kind(), pdu.state_key()), pdu_id);
}
} }
db.rooms.force_state(room_id, state, &db.globals)?; // We append to state before appending the pdu, so we don't have a moment in time with the
// pdu without it's state. This is okay because append_pdu can't fail.
let statehashid = db.rooms.append_to_state(&pdu, &db.globals)?;
db.rooms.append_pdu(
&pdu,
utils::to_canonical_object(&pdu).expect("Pdu is valid canonical object"),
db.globals.next_count()?,
pdu_id.into(),
&[pdu.event_id.clone()],
db,
)?;
// We set the room state after inserting the pdu, so that we never have a moment in time
// where events in the current room state do not exist
db.rooms.set_room_state(&room_id, statehashid)?;
} else { } else {
let event = member::MemberEventContent { let event = member::MemberEventContent {
membership: member::MembershipState::Join, membership: member::MembershipState::Join,
@ -730,13 +649,50 @@ async fn join_room_by_id_helper(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
db.flush().await?;
Ok(join_room_by_id::Response::new(room_id.clone()).into()) Ok(join_room_by_id::Response::new(room_id.clone()).into())
} }
async fn validate_and_add_event_id(
pdu: &Raw<Pdu>,
room_version: &RoomVersionId,
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, String>>>,
db: &Database,
) -> Result<(EventId, CanonicalJsonObject)> {
let mut value = serde_json::from_str::<CanonicalJsonObject>(pdu.json().get()).map_err(|e| {
error!("{:?}: {:?}", pdu, e);
Error::BadServerResponse("Invalid PDU in server response")
})?;
server_server::fetch_required_signing_keys(&value, pub_key_map, db).await?;
if let Err(e) = ruma::signatures::verify_event(
&*pub_key_map
.read()
.map_err(|_| Error::bad_database("RwLock is poisoned."))?,
&value,
room_version,
) {
warn!("Event failed verification: {}", e);
return Err(Error::BadServerResponse("Event failed verification."));
}
let event_id = EventId::try_from(&*format!(
"${}",
ruma::signatures::reference_hash(&value, &room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
value.insert(
"event_id".to_owned(),
to_canonical_value(&event_id)
.expect("a valid EventId can be converted to CanonicalJsonValue"),
);
Ok((event_id, value))
}

@ -8,7 +8,10 @@ use ruma::{
events::EventContent, events::EventContent,
EventId, EventId,
}; };
use std::convert::{TryFrom, TryInto}; use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; use rocket::{get, put};
@ -47,7 +50,7 @@ pub async fn send_message_event_route(
return Ok(send_message_event::Response { event_id }.into()); return Ok(send_message_event::Response { event_id }.into());
} }
let mut unsigned = serde_json::Map::new(); let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into()); unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into());
let event_id = db.rooms.build_and_append_pdu( let event_id = db.rooms.build_and_append_pdu(
@ -66,11 +69,7 @@ pub async fn send_message_event_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.transaction_ids.add_txnid( db.transaction_ids.add_txnid(

@ -49,7 +49,6 @@ pub async fn set_displayname_route(
"Tried to send displayname update for user not in the room.", "Tried to send displayname update for user not in the room.",
) )
})? })?
.1
.content .content
.clone(), .clone(),
) )
@ -64,11 +63,7 @@ pub async fn set_displayname_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Presence update // Presence update
@ -148,7 +143,6 @@ pub async fn set_avatar_url_route(
"Tried to send avatar url update for user not in the room.", "Tried to send avatar url update for user not in the room.",
) )
})? })?
.1
.content .content
.clone(), .clone(),
) )
@ -163,11 +157,7 @@ pub async fn set_avatar_url_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Presence update // Presence update

@ -5,14 +5,12 @@ use ruma::{
error::ErrorKind, error::ErrorKind,
r0::push::{ r0::push::{
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled, delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
get_pushrules_all, set_pushrule, set_pushrule_actions, set_pushrule_enabled, RuleKind, get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
set_pushrule_enabled, RuleKind,
}, },
}, },
events::EventType, events::{push_rules, EventType},
push::{ push::{ConditionalPushRuleInit, PatternedPushRuleInit, SimplePushRuleInit},
ConditionalPushRuleInit, ContentPushRule, OverridePushRule, PatternedPushRuleInit,
RoomPushRule, SenderPushRule, SimplePushRuleInit, UnderridePushRule,
},
}; };
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
@ -31,7 +29,7 @@ pub async fn get_pushrules_all_route(
let event = db let event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -56,7 +54,7 @@ pub async fn get_pushrule_route(
let event = db let event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -66,49 +64,48 @@ pub async fn get_pushrule_route(
let rule = match body.kind { let rule = match body.kind {
RuleKind::Override => global RuleKind::Override => global
.override_ .override_
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.clone().into()),
.map(|rule| rule.0.clone().into()),
RuleKind::Underride => global RuleKind::Underride => global
.underride .underride
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.clone().into()),
.map(|rule| rule.0.clone().into()),
RuleKind::Sender => global RuleKind::Sender => global
.sender .sender
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.clone().into()),
.map(|rule| rule.0.clone().into()),
RuleKind::Room => global RuleKind::Room => global
.room .room
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.clone().into()),
.map(|rule| rule.0.clone().into()),
RuleKind::Content => global RuleKind::Content => global
.content .content
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.clone().into()),
.map(|rule| rule.0.clone().into()),
RuleKind::_Custom(_) => None, RuleKind::_Custom(_) => None,
}; };
if let Some(rule) = rule { if let Some(rule) = rule {
Ok(get_pushrule::Response { rule }.into()) Ok(get_pushrule::Response { rule }.into())
} else { } else {
Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")) Err(Error::BadRequest(
ErrorKind::NotFound,
"Push rule not found.",
))
} }
} }
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>") put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<req>")
)] )]
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, req))]
pub async fn set_pushrule_route( pub async fn set_pushrule_route(
db: State<'_, Database>, db: State<'_, Database>,
body: Ruma<set_pushrule::Request<'_>>, req: Ruma<set_pushrule::Request<'_>>,
) -> ConduitResult<set_pushrule::Response> { ) -> ConduitResult<set_pushrule::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = req.sender_user.as_ref().expect("user is authenticated");
let body = req.body;
if body.scope != "global" { if body.scope != "global" {
return Err(Error::BadRequest( return Err(Error::BadRequest(
@ -119,7 +116,7 @@ pub async fn set_pushrule_route(
let mut event = db let mut event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -128,107 +125,62 @@ pub async fn set_pushrule_route(
let global = &mut event.content.global; let global = &mut event.content.global;
match body.kind { match body.kind {
RuleKind::Override => { RuleKind::Override => {
if let Some(rule) = global global.override_.replace(
.override_
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.override_.remove(&rule);
}
global.override_.insert(OverridePushRule(
ConditionalPushRuleInit { ConditionalPushRuleInit {
actions: body.actions.clone(), actions: body.actions,
default: false, default: false,
enabled: true, enabled: true,
rule_id: body.rule_id.clone(), rule_id: body.rule_id,
conditions: body.conditions.clone(), conditions: body.conditions,
} }
.into(), .into(),
)); );
} }
RuleKind::Underride => { RuleKind::Underride => {
if let Some(rule) = global global.underride.replace(
.underride
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.underride.remove(&rule);
}
global.underride.insert(UnderridePushRule(
ConditionalPushRuleInit { ConditionalPushRuleInit {
actions: body.actions.clone(), actions: body.actions,
default: false, default: false,
enabled: true, enabled: true,
rule_id: body.rule_id.clone(), rule_id: body.rule_id,
conditions: body.conditions.clone(), conditions: body.conditions,
} }
.into(), .into(),
)); );
} }
RuleKind::Sender => { RuleKind::Sender => {
if let Some(rule) = global global.sender.replace(
.sender
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.sender.remove(&rule);
}
global.sender.insert(SenderPushRule(
SimplePushRuleInit { SimplePushRuleInit {
actions: body.actions.clone(), actions: body.actions,
default: false, default: false,
enabled: true, enabled: true,
rule_id: body.rule_id.clone(), rule_id: body.rule_id,
} }
.into(), .into(),
)); );
} }
RuleKind::Room => { RuleKind::Room => {
if let Some(rule) = global global.room.replace(
.room
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.room.remove(&rule);
}
global.room.insert(RoomPushRule(
SimplePushRuleInit { SimplePushRuleInit {
actions: body.actions.clone(), actions: body.actions,
default: false, default: false,
enabled: true, enabled: true,
rule_id: body.rule_id.clone(), rule_id: body.rule_id,
} }
.into(), .into(),
)); );
} }
RuleKind::Content => { RuleKind::Content => {
if let Some(rule) = global global.content.replace(
.content
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.content.remove(&rule);
}
global.content.insert(ContentPushRule(
PatternedPushRuleInit { PatternedPushRuleInit {
actions: body.actions.clone(), actions: body.actions,
default: false, default: false,
enabled: true, enabled: true,
rule_id: body.rule_id.clone(), rule_id: body.rule_id,
pattern: body.pattern.clone().unwrap_or_default(), pattern: body.pattern.unwrap_or_default(),
} }
.into(), .into(),
)); );
} }
RuleKind::_Custom(_) => {} RuleKind::_Custom(_) => {}
} }
@ -266,7 +218,7 @@ pub async fn get_pushrule_actions_route(
let mut event = db let mut event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -276,29 +228,24 @@ pub async fn get_pushrule_actions_route(
let actions = match body.kind { let actions = match body.kind {
RuleKind::Override => global RuleKind::Override => global
.override_ .override_
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.actions.clone()),
.map(|rule| rule.0.actions.clone()),
RuleKind::Underride => global RuleKind::Underride => global
.underride .underride
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.actions.clone()),
.map(|rule| rule.0.actions.clone()),
RuleKind::Sender => global RuleKind::Sender => global
.sender .sender
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.actions.clone()),
.map(|rule| rule.0.actions.clone()),
RuleKind::Room => global RuleKind::Room => global
.room .room
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.actions.clone()),
.map(|rule| rule.0.actions.clone()),
RuleKind::Content => global RuleKind::Content => global
.content .content
.iter() .get(body.rule_id.as_str())
.find(|rule| rule.0.rule_id == body.rule_id) .map(|rule| rule.actions.clone()),
.map(|rule| rule.0.actions.clone()),
RuleKind::_Custom(_) => None, RuleKind::_Custom(_) => None,
}; };
@ -330,7 +277,7 @@ pub async fn set_pushrule_actions_route(
let mut event = db let mut event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -339,63 +286,33 @@ pub async fn set_pushrule_actions_route(
let global = &mut event.content.global; let global = &mut event.content.global;
match body.kind { match body.kind {
RuleKind::Override => { RuleKind::Override => {
if let Some(mut rule) = global if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
.override_ rule.actions = body.actions.clone();
.iter() global.override_.replace(rule);
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.override_.remove(&rule);
rule.0.actions = body.actions.clone();
global.override_.insert(rule);
} }
} }
RuleKind::Underride => { RuleKind::Underride => {
if let Some(mut rule) = global if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
.underride rule.actions = body.actions.clone();
.iter() global.underride.replace(rule);
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.underride.remove(&rule);
rule.0.actions = body.actions.clone();
global.underride.insert(rule);
} }
} }
RuleKind::Sender => { RuleKind::Sender => {
if let Some(mut rule) = global if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
.sender rule.actions = body.actions.clone();
.iter() global.sender.replace(rule);
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.sender.remove(&rule);
rule.0.actions = body.actions.clone();
global.sender.insert(rule);
} }
} }
RuleKind::Room => { RuleKind::Room => {
if let Some(mut rule) = global if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
.room rule.actions = body.actions.clone();
.iter() global.room.replace(rule);
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.room.remove(&rule);
rule.0.actions = body.actions.clone();
global.room.insert(rule);
} }
} }
RuleKind::Content => { RuleKind::Content => {
if let Some(mut rule) = global if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
.content rule.actions = body.actions.clone();
.iter() global.content.replace(rule);
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.content.remove(&rule);
rule.0.actions = body.actions.clone();
global.content.insert(rule);
} }
} }
RuleKind::_Custom(_) => {} RuleKind::_Custom(_) => {}
@ -434,7 +351,7 @@ pub async fn get_pushrule_enabled_route(
let mut event = db let mut event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -445,28 +362,28 @@ pub async fn get_pushrule_enabled_route(
RuleKind::Override => global RuleKind::Override => global
.override_ .override_
.iter() .iter()
.find(|rule| rule.0.rule_id == body.rule_id) .find(|rule| rule.rule_id == body.rule_id)
.map_or(false, |rule| rule.0.enabled), .map_or(false, |rule| rule.enabled),
RuleKind::Underride => global RuleKind::Underride => global
.underride .underride
.iter() .iter()
.find(|rule| rule.0.rule_id == body.rule_id) .find(|rule| rule.rule_id == body.rule_id)
.map_or(false, |rule| rule.0.enabled), .map_or(false, |rule| rule.enabled),
RuleKind::Sender => global RuleKind::Sender => global
.sender .sender
.iter() .iter()
.find(|rule| rule.0.rule_id == body.rule_id) .find(|rule| rule.rule_id == body.rule_id)
.map_or(false, |rule| rule.0.enabled), .map_or(false, |rule| rule.enabled),
RuleKind::Room => global RuleKind::Room => global
.room .room
.iter() .iter()
.find(|rule| rule.0.rule_id == body.rule_id) .find(|rule| rule.rule_id == body.rule_id)
.map_or(false, |rule| rule.0.enabled), .map_or(false, |rule| rule.enabled),
RuleKind::Content => global RuleKind::Content => global
.content .content
.iter() .iter()
.find(|rule| rule.0.rule_id == body.rule_id) .find(|rule| rule.rule_id == body.rule_id)
.map_or(false, |rule| rule.0.enabled), .map_or(false, |rule| rule.enabled),
RuleKind::_Custom(_) => false, RuleKind::_Custom(_) => false,
}; };
@ -504,62 +421,37 @@ pub async fn set_pushrule_enabled_route(
let global = &mut event.content.global; let global = &mut event.content.global;
match body.kind { match body.kind {
RuleKind::Override => { RuleKind::Override => {
if let Some(mut rule) = global if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
.override_
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.override_.remove(&rule); global.override_.remove(&rule);
rule.0.enabled = body.enabled; rule.enabled = body.enabled;
global.override_.insert(rule); global.override_.insert(rule);
} }
} }
RuleKind::Underride => { RuleKind::Underride => {
if let Some(mut rule) = global if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
.underride
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.underride.remove(&rule); global.underride.remove(&rule);
rule.0.enabled = body.enabled; rule.enabled = body.enabled;
global.underride.insert(rule); global.underride.insert(rule);
} }
} }
RuleKind::Sender => { RuleKind::Sender => {
if let Some(mut rule) = global if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
.sender
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.sender.remove(&rule); global.sender.remove(&rule);
rule.0.enabled = body.enabled; rule.enabled = body.enabled;
global.sender.insert(rule); global.sender.insert(rule);
} }
} }
RuleKind::Room => { RuleKind::Room => {
if let Some(mut rule) = global if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
.room
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.room.remove(&rule); global.room.remove(&rule);
rule.0.enabled = body.enabled; rule.enabled = body.enabled;
global.room.insert(rule); global.room.insert(rule);
} }
} }
RuleKind::Content => { RuleKind::Content => {
if let Some(mut rule) = global if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
.content
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.content.remove(&rule); global.content.remove(&rule);
rule.0.enabled = body.enabled; rule.enabled = body.enabled;
global.content.insert(rule); global.content.insert(rule);
} }
} }
@ -599,7 +491,7 @@ pub async fn delete_pushrule_route(
let mut event = db let mut event = db
.account_data .account_data
.get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)? .get::<push_rules::PushRulesEvent>(None, &sender_user, EventType::PushRules)?
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"PushRules event not found.", "PushRules event not found.",
@ -608,52 +500,27 @@ pub async fn delete_pushrule_route(
let global = &mut event.content.global; let global = &mut event.content.global;
match body.kind { match body.kind {
RuleKind::Override => { RuleKind::Override => {
if let Some(rule) = global if let Some(rule) = global.override_.get(body.rule_id.as_str()).cloned() {
.override_
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.override_.remove(&rule); global.override_.remove(&rule);
} }
} }
RuleKind::Underride => { RuleKind::Underride => {
if let Some(rule) = global if let Some(rule) = global.underride.get(body.rule_id.as_str()).cloned() {
.underride
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.underride.remove(&rule); global.underride.remove(&rule);
} }
} }
RuleKind::Sender => { RuleKind::Sender => {
if let Some(rule) = global if let Some(rule) = global.sender.get(body.rule_id.as_str()).cloned() {
.sender
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.sender.remove(&rule); global.sender.remove(&rule);
} }
} }
RuleKind::Room => { RuleKind::Room => {
if let Some(rule) = global if let Some(rule) = global.room.get(body.rule_id.as_str()).cloned() {
.room
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.room.remove(&rule); global.room.remove(&rule);
} }
} }
RuleKind::Content => { RuleKind::Content => {
if let Some(rule) = global if let Some(rule) = global.content.get(body.rule_id.as_str()).cloned() {
.content
.iter()
.find(|rule| rule.0.rule_id == body.rule_id)
.cloned()
{
global.content.remove(&rule); global.content.remove(&rule);
} }
} }
@ -673,22 +540,38 @@ pub async fn delete_pushrule_route(
Ok(delete_pushrule::Response.into()) Ok(delete_pushrule::Response.into())
} }
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/pushers"))] #[cfg_attr(
#[tracing::instrument] feature = "conduit_bin",
pub async fn get_pushers_route() -> ConduitResult<get_pushers::Response> { get("/_matrix/client/r0/pushers", data = "<body>")
)]
#[tracing::instrument(skip(db, body))]
pub async fn get_pushers_route(
db: State<'_, Database>,
body: Ruma<get_pushers::Request>,
) -> ConduitResult<get_pushers::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(get_pushers::Response { Ok(get_pushers::Response {
pushers: Vec::new(), pushers: db.pusher.get_pushers(sender_user)?,
} }
.into()) .into())
} }
#[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/pushers/set"))] #[cfg_attr(
#[tracing::instrument(skip(db))] feature = "conduit_bin",
pub async fn set_pushers_route(db: State<'_, Database>) -> ConduitResult<get_pushers::Response> { post("/_matrix/client/r0/pushers/set", data = "<body>")
)]
#[tracing::instrument(skip(db, body))]
pub async fn set_pushers_route(
db: State<'_, Database>,
body: Ruma<set_pusher::Request>,
) -> ConduitResult<set_pusher::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let pusher = body.pusher.clone();
db.pusher.set_pusher(sender_user, pusher)?;
db.flush().await?; db.flush().await?;
Ok(get_pushers::Response { Ok(set_pusher::Response::default().into())
pushers: Vec::new(),
}
.into())
} }

@ -47,6 +47,8 @@ pub async fn set_read_marker_route(
))?, ))?,
&db.globals, &db.globals,
)?; )?;
db.rooms
.reset_notification_counts(&sender_user, &body.room_id)?;
let mut user_receipts = BTreeMap::new(); let mut user_receipts = BTreeMap::new();
user_receipts.insert( user_receipts.insert(
@ -103,6 +105,8 @@ pub async fn create_receipt_route(
))?, ))?,
&db.globals, &db.globals,
)?; )?;
db.rooms
.reset_notification_counts(&sender_user, &body.room_id)?;
let mut user_receipts = BTreeMap::new(); let mut user_receipts = BTreeMap::new();
user_receipts.insert( user_receipts.insert(

@ -32,11 +32,7 @@ pub async fn redact_event_route(
}, },
&sender_user, &sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;

@ -66,11 +66,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 2. Let the room creator join // 2. Let the room creator join
@ -91,18 +87,28 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 3. Power levels // 3. Power levels
// Figure out preset. We need it for preset specific events
let preset = body
.preset
.clone()
.unwrap_or_else(|| match &body.visibility {
room::Visibility::Private => create_room::RoomPreset::PrivateChat,
room::Visibility::Public => create_room::RoomPreset::PublicChat,
room::Visibility::_Custom(_) => create_room::RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut users = BTreeMap::new(); let mut users = BTreeMap::new();
users.insert(sender_user.clone(), 100.into()); users.insert(sender_user.clone(), 100.into());
for invite_ in &body.invite {
users.insert(invite_.clone(), 100.into()); if preset == create_room::RoomPreset::TrustedPrivateChat {
for invite_ in &body.invite {
users.insert(invite_.clone(), 100.into());
}
} }
let power_levels_content = if let Some(power_levels) = &body.power_level_content_override { let power_levels_content = if let Some(power_levels) = &body.power_level_content_override {
@ -136,25 +142,11 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4. Events set by preset // 4. Events set by preset
// Figure out preset. We need it for preset specific events
let preset = body
.preset
.clone()
.unwrap_or_else(|| match &body.visibility {
room::Visibility::Private => create_room::RoomPreset::PrivateChat,
room::Visibility::Public => create_room::RoomPreset::PublicChat,
room::Visibility::_Custom(s) => create_room::RoomPreset::_Custom(s.into()),
});
// 4.1 Join Rules // 4.1 Join Rules
db.rooms.build_and_append_pdu( db.rooms.build_and_append_pdu(
PduBuilder { PduBuilder {
@ -176,11 +168,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4.2 History Visibility // 4.2 History Visibility
@ -197,11 +185,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 4.3 Guest Access // 4.3 Guest Access
@ -226,11 +210,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// 5. Events listed in initial_state // 5. Events listed in initial_state
@ -245,16 +225,8 @@ pub async fn create_room_route(
continue; continue;
} }
db.rooms.build_and_append_pdu( db.rooms
pdu_builder, .build_and_append_pdu(pdu_builder, &sender_user, &room_id, &db)?;
&sender_user,
&room_id,
&db.globals,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?;
} }
// 6. Events implied by name and topic // 6. Events implied by name and topic
@ -274,11 +246,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -296,11 +264,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -323,11 +287,7 @@ pub async fn create_room_route(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -387,10 +347,7 @@ pub async fn upgrade_room_route(
) -> ConduitResult<upgrade_room::Response> { ) -> ConduitResult<upgrade_room::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !matches!( if !matches!(body.new_version, RoomVersionId::Version6) {
body.new_version,
RoomVersionId::Version5 | RoomVersionId::Version6
) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::UnsupportedRoomVersion, ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.", "This server does not support that room version.",
@ -416,11 +373,7 @@ pub async fn upgrade_room_route(
}, },
sender_user, sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Get the old room federations status // Get the old room federations status
@ -428,7 +381,6 @@ pub async fn upgrade_room_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomCreate, "")? .room_state_get(&body.room_id, &EventType::RoomCreate, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.1
.content, .content,
) )
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
@ -460,11 +412,7 @@ pub async fn upgrade_room_route(
}, },
sender_user, sender_user,
&replacement_room, &replacement_room,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Join the new room // Join the new room
@ -485,11 +433,7 @@ pub async fn upgrade_room_route(
}, },
sender_user, sender_user,
&replacement_room, &replacement_room,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
// Recommended transferable state events list from the specs // Recommended transferable state events list from the specs
@ -508,7 +452,7 @@ pub async fn upgrade_room_route(
// Replicate transferable state events to the new room // Replicate transferable state events to the new room
for event_type in transferable_state_events { for event_type in transferable_state_events {
let event_content = match db.rooms.room_state_get(&body.room_id, &event_type, "")? { let event_content = match db.rooms.room_state_get(&body.room_id, &event_type, "")? {
Some((_, v)) => v.content.clone(), Some(v) => v.content.clone(),
None => continue, // Skipping missing events. None => continue, // Skipping missing events.
}; };
@ -522,11 +466,7 @@ pub async fn upgrade_room_route(
}, },
sender_user, sender_user,
&replacement_room, &replacement_room,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
} }
@ -542,7 +482,6 @@ pub async fn upgrade_room_route(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")? .room_state_get(&body.room_id, &EventType::RoomPowerLevels, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))? .ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.1
.content, .content,
) )
.expect("database contains invalid PDU") .expect("database contains invalid PDU")
@ -569,11 +508,7 @@ pub async fn upgrade_room_route(
}, },
sender_user, sender_user,
&body.room_id, &body.room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
db.flush().await?; db.flush().await?;

@ -51,8 +51,11 @@ pub async fn login_route(
// Validate login method // Validate login method
// TODO: Other login methods // TODO: Other login methods
let user_id = match &body.login_info { let user_id = match &body.login_info {
login::IncomingLoginInfo::Password { password } => { login::IncomingLoginInfo::Password {
let username = if let login::IncomingUserInfo::MatrixId(matrix_id) = &body.user { identifier,
password,
} => {
let username = if let login::IncomingUserIdentifier::MatrixId(matrix_id) = identifier {
matrix_id matrix_id
} else { } else {
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type.")); return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));

@ -3,10 +3,7 @@ use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
r0::state::{ r0::state::{get_state_events, get_state_events_for_key, send_state_event},
get_state_events, get_state_events_for_empty_key, get_state_events_for_key,
send_state_event_for_empty_key, send_state_event_for_key,
},
}, },
events::{ events::{
room::history_visibility::{HistoryVisibility, HistoryVisibilityEventContent}, room::history_visibility::{HistoryVisibility, HistoryVisibilityEventContent},
@ -25,8 +22,8 @@ use rocket::{get, put};
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_key_route( pub async fn send_state_event_for_key_route(
db: State<'_, Database>, db: State<'_, Database>,
body: Ruma<send_state_event_for_key::Request<'_>>, body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event_for_key::Response> { ) -> ConduitResult<send_state_event::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let content = serde_json::from_str::<serde_json::Value>( let content = serde_json::from_str::<serde_json::Value>(
@ -49,7 +46,7 @@ pub async fn send_state_event_for_key_route(
db.flush().await?; db.flush().await?;
Ok(send_state_event_for_key::Response { event_id }.into()) Ok(send_state_event::Response { event_id }.into())
} }
#[cfg_attr( #[cfg_attr(
@ -59,8 +56,8 @@ pub async fn send_state_event_for_key_route(
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn send_state_event_for_empty_key_route( pub async fn send_state_event_for_empty_key_route(
db: State<'_, Database>, db: State<'_, Database>,
body: Ruma<send_state_event_for_empty_key::Request<'_>>, body: Ruma<send_state_event::Request<'_>>,
) -> ConduitResult<send_state_event_for_empty_key::Response> { ) -> ConduitResult<send_state_event::Response> {
// This just calls send_state_event_for_key_route // This just calls send_state_event_for_key_route
let Ruma { let Ruma {
body, body,
@ -81,7 +78,7 @@ pub async fn send_state_event_for_empty_key_route(
&db, &db,
sender_user sender_user
.as_ref() .as_ref()
.expect("no user for send state empty key rout"), .expect("no user for send state empty key route"),
&body.content, &body.content,
json, json,
&body.room_id, &body.room_id,
@ -91,7 +88,7 @@ pub async fn send_state_event_for_empty_key_route(
db.flush().await?; db.flush().await?;
Ok(send_state_event_for_empty_key::Response { event_id }.into()) Ok(send_state_event::Response { event_id }.into())
} }
#[cfg_attr( #[cfg_attr(
@ -112,7 +109,7 @@ pub async fn get_state_events_route(
&& !matches!( && !matches!(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|(_, event)| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
@ -159,7 +156,7 @@ pub async fn get_state_events_for_key_route(
&& !matches!( && !matches!(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|(_, event)| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
@ -183,8 +180,7 @@ pub async fn get_state_events_for_key_route(
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"State event not found.", "State event not found.",
))? ))?;
.1;
Ok(get_state_events_for_key::Response { Ok(get_state_events_for_key::Response {
content: serde_json::value::to_raw_value(&event.content) content: serde_json::value::to_raw_value(&event.content)
@ -200,8 +196,8 @@ pub async fn get_state_events_for_key_route(
#[tracing::instrument(skip(db, body))] #[tracing::instrument(skip(db, body))]
pub async fn get_state_events_for_empty_key_route( pub async fn get_state_events_for_empty_key_route(
db: State<'_, Database>, db: State<'_, Database>,
body: Ruma<get_state_events_for_empty_key::Request<'_>>, body: Ruma<get_state_events_for_key::Request<'_>>,
) -> ConduitResult<get_state_events_for_empty_key::Response> { ) -> ConduitResult<get_state_events_for_key::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
#[allow(clippy::blocks_in_if_conditions)] #[allow(clippy::blocks_in_if_conditions)]
@ -211,7 +207,7 @@ pub async fn get_state_events_for_empty_key_route(
&& !matches!( && !matches!(
db.rooms db.rooms
.room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")? .room_state_get(&body.room_id, &EventType::RoomHistoryVisibility, "")?
.map(|(_, event)| { .map(|event| {
serde_json::from_value::<HistoryVisibilityEventContent>(event.content) serde_json::from_value::<HistoryVisibilityEventContent>(event.content)
.map_err(|_| { .map_err(|_| {
Error::bad_database( Error::bad_database(
@ -235,10 +231,9 @@ pub async fn get_state_events_for_empty_key_route(
.ok_or(Error::BadRequest( .ok_or(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"State event not found.", "State event not found.",
))? ))?;
.1;
Ok(get_state_events_for_empty_key::Response { Ok(get_state_events_for_key::Response {
content: serde_json::value::to_raw_value(&event.content) content: serde_json::value::to_raw_value(&event.content)
.map_err(|_| Error::bad_database("Invalid event content in database"))?, .map_err(|_| Error::bad_database("Invalid event content in database"))?,
} }
@ -289,11 +284,7 @@ pub async fn send_state_event_for_key_helper(
}, },
&sender_user, &sender_user,
&room_id, &room_id,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
)?; )?;
Ok(event_id) Ok(event_id)

@ -11,7 +11,7 @@ use ruma::{
use rocket::{get, tokio}; use rocket::{get, tokio};
use std::{ use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet}, collections::{hash_map, BTreeMap, HashMap, HashSet},
convert::TryFrom, convert::{TryFrom, TryInto},
time::Duration, time::Duration,
}; };
@ -96,17 +96,24 @@ pub async fn sync_events_route(
// Database queries: // Database queries:
let current_state_hash = db.rooms.current_state_hash(&room_id)?; let current_shortstatehash = db.rooms.current_shortstatehash(&room_id)?;
// These type is Option<Option<_>>. The outer Option is None when there is no event between // These type is Option<Option<_>>. The outer Option is None when there is no event between
// since and the current room state, meaning there should be no updates. // since and the current room state, meaning there should be no updates.
// The inner Option is None when there is an event, but there is no state hash associated // The inner Option is None when there is an event, but there is no state hash associated
// with it. This can happen for the RoomCreate event, so all updates should arrive. // with it. This can happen for the RoomCreate event, so all updates should arrive.
let first_pdu_after_since = db.rooms.pdus_after(sender_user, &room_id, since).next(); let first_pdu_before_since = db.rooms.pdus_until(sender_user, &room_id, since).next();
let pdus_after_since = db
.rooms
.pdus_after(sender_user, &room_id, since)
.next()
.is_some();
let since_state_hash = first_pdu_after_since let since_shortstatehash = first_pdu_before_since.as_ref().map(|pdu| {
.as_ref() db.rooms
.map(|pdu| db.rooms.pdu_state_hash(&pdu.as_ref().ok()?.0).ok()?); .pdu_shortstatehash(&pdu.as_ref().ok()?.1.event_id)
.ok()?
});
let ( let (
heroes, heroes,
@ -114,7 +121,7 @@ pub async fn sync_events_route(
invited_member_count, invited_member_count,
joined_since_last_sync, joined_since_last_sync,
state_events, state_events,
) = if since_state_hash != None && Some(&current_state_hash) != since_state_hash.as_ref() { ) = if pdus_after_since && Some(current_shortstatehash) != since_shortstatehash {
let current_state = db.rooms.room_state_full(&room_id)?; let current_state = db.rooms.room_state_full(&room_id)?;
let current_members = current_state let current_members = current_state
.iter() .iter()
@ -124,11 +131,16 @@ pub async fn sync_events_route(
let encrypted_room = current_state let encrypted_room = current_state
.get(&(EventType::RoomEncryption, "".to_owned())) .get(&(EventType::RoomEncryption, "".to_owned()))
.is_some(); .is_some();
let since_state = since_state_hash.as_ref().map(|state_hash| { let since_state = since_shortstatehash
state_hash .as_ref()
.as_ref() .map(|since_shortstatehash| {
.and_then(|state_hash| db.rooms.state_full(&room_id, &state_hash).ok()) Ok::<_, Error>(
}); since_shortstatehash
.map(|since_shortstatehash| db.rooms.state_full(since_shortstatehash))
.transpose()?,
)
})
.transpose()?;
let since_encryption = since_state.as_ref().map(|state| { let since_encryption = since_state.as_ref().map(|state| {
state state
@ -138,9 +150,9 @@ pub async fn sync_events_route(
// Calculations: // Calculations:
let new_encrypted_room = let new_encrypted_room =
encrypted_room && since_encryption.map_or(false, |encryption| encryption.is_none()); encrypted_room && since_encryption.map_or(true, |encryption| encryption.is_none());
let send_member_count = since_state.as_ref().map_or(false, |since_state| { let send_member_count = since_state.as_ref().map_or(true, |since_state| {
since_state.as_ref().map_or(true, |since_state| { since_state.as_ref().map_or(true, |since_state| {
current_members.len() current_members.len()
!= since_state != since_state
@ -179,7 +191,7 @@ pub async fn sync_events_route(
let since_membership = let since_membership =
since_state since_state
.as_ref() .as_ref()
.map_or(MembershipState::Join, |since_state| { .map_or(MembershipState::Leave, |since_state| {
since_state since_state
.as_ref() .as_ref()
.and_then(|since_state| { .and_then(|since_state| {
@ -221,7 +233,7 @@ pub async fn sync_events_route(
} }
} }
let joined_since_last_sync = since_sender_member.map_or(false, |member| { let joined_since_last_sync = since_sender_member.map_or(true, |member| {
member.map_or(true, |member| member.membership != MembershipState::Join) member.map_or(true, |member| member.membership != MembershipState::Join)
}); });
@ -357,23 +369,23 @@ pub async fn sync_events_route(
); );
let notification_count = if send_notification_counts { let notification_count = if send_notification_counts {
if let Some(last_read) = db.rooms.edus.private_read_get(&room_id, &sender_user)? { Some(
Some( db.rooms
(db.rooms .notification_count(&sender_user, &room_id)?
.pdus_since(&sender_user, &room_id, last_read)? .try_into()
.filter_map(|pdu| pdu.ok()) // Filter out buggy events .expect("notification count can't go that high"),
.filter(|(_, pdu)| { )
matches!( } else {
pdu.kind.clone(), None
EventType::RoomMessage | EventType::RoomEncrypted };
)
}) let highlight_count = if send_notification_counts {
.count() as u32) Some(
.into(), db.rooms
) .highlight_count(&sender_user, &room_id)?
} else { .try_into()
None .expect("highlight count can't go that high"),
} )
} else { } else {
None None
}; };
@ -427,7 +439,7 @@ pub async fn sync_events_route(
invited_member_count: invited_member_count.map(|n| (n as u32).into()), invited_member_count: invited_member_count.map(|n| (n as u32).into()),
}, },
unread_notifications: sync_events::UnreadNotificationsCount { unread_notifications: sync_events::UnreadNotificationsCount {
highlight_count: None, highlight_count,
notification_count, notification_count,
}, },
timeline: sync_events::Timeline { timeline: sync_events::Timeline {
@ -481,85 +493,17 @@ pub async fn sync_events_route(
} }
let mut left_rooms = BTreeMap::new(); let mut left_rooms = BTreeMap::new();
for room_id in db.rooms.rooms_left(&sender_user) { for result in db.rooms.rooms_left(&sender_user) {
let room_id = room_id?; let (room_id, left_state_events) = result?;
let left_count = db.rooms.get_left_count(&room_id, &sender_user)?;
let since_member = if let Some(since_member) = db // Left before last sync
.rooms if Some(since) >= left_count {
.pdus_after(sender_user, &room_id, since)
.next()
.and_then(|pdu| pdu.ok())
.and_then(|pdu| {
db.rooms
.pdu_state_hash(&pdu.0)
.ok()?
.ok_or_else(|| Error::bad_database("Pdu in db doesn't have a state hash."))
.ok()
})
.and_then(|state_hash| {
db.rooms
.state_get(
&room_id,
&state_hash,
&EventType::RoomMember,
sender_user.as_str(),
)
.ok()?
.ok_or_else(|| Error::bad_database("State hash in db doesn't have a state."))
.ok()
})
.and_then(|(pdu_id, pdu)| {
serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
pdu.content.clone(),
)
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PDU in database."))
.map(|content| (pdu_id, pdu, content))
.ok()
}) {
since_member
} else {
// We couldn't find the since_member event. This is very weird - we better abort
continue; continue;
}; }
let left_since_last_sync = since_member.2.membership == MembershipState::Join;
let left_room = if left_since_last_sync {
device_list_left.extend(
db.rooms
.room_members(&room_id)
.filter_map(|user_id| Some(user_id.ok()?))
.filter(|user_id| {
// Don't send key updates from the sender to the sender
sender_user != user_id
})
.filter(|user_id| {
// Only send if the sender doesn't share any encrypted room with the target
// anymore
!share_encrypted_room(&db, sender_user, user_id, &room_id)
}),
);
let pdus = db.rooms.pdus_since(&sender_user, &room_id, since)?;
let mut room_events = pdus
.filter_map(|pdu| pdu.ok()) // Filter out buggy events
.take_while(|(pdu_id, _)| since_member.0 != pdu_id)
.map(|(_, pdu)| pdu.to_sync_room_event())
.collect::<Vec<_>>();
room_events.push(since_member.1.to_sync_room_event());
sync_events::LeftRoom { left_rooms.insert(
account_data: sync_events::AccountData { events: Vec::new() }, room_id.clone(),
timeline: sync_events::Timeline {
limited: false,
prev_batch: Some(next_batch.clone()),
events: room_events,
},
state: sync_events::State { events: Vec::new() },
}
} else {
sync_events::LeftRoom { sync_events::LeftRoom {
account_data: sync_events::AccountData { events: Vec::new() }, account_data: sync_events::AccountData { events: Vec::new() },
timeline: sync_events::Timeline { timeline: sync_events::Timeline {
@ -567,54 +511,31 @@ pub async fn sync_events_route(
prev_batch: Some(next_batch.clone()), prev_batch: Some(next_batch.clone()),
events: Vec::new(), events: Vec::new(),
}, },
state: sync_events::State { events: Vec::new() }, state: sync_events::State {
} events: left_state_events,
}; },
},
if !left_room.is_empty() { );
left_rooms.insert(room_id.clone(), left_room);
}
} }
let mut invited_rooms = BTreeMap::new(); let mut invited_rooms = BTreeMap::new();
for room_id in db.rooms.rooms_invited(&sender_user) { for result in db.rooms.rooms_invited(&sender_user) {
let room_id = room_id?; let (room_id, invite_state_events) = result?;
let mut invited_since_last_sync = false; let invite_count = db.rooms.get_invite_count(&room_id, &sender_user)?;
for pdu in db.rooms.pdus_since(&sender_user, &room_id, since)? {
let (_, pdu) = pdu?;
if pdu.kind == EventType::RoomMember && pdu.state_key == Some(sender_user.to_string()) {
let content = serde_json::from_value::<
Raw<ruma::events::room::member::MemberEventContent>,
>(pdu.content.clone())
.expect("Raw::from_value always works")
.deserialize()
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
if content.membership == MembershipState::Invite {
invited_since_last_sync = true;
break;
}
}
}
if !invited_since_last_sync { // Invited before last sync
if Some(since) >= invite_count {
continue; continue;
} }
let invited_room = sync_events::InvitedRoom { invited_rooms.insert(
invite_state: sync_events::InviteState { room_id.clone(),
events: db sync_events::InvitedRoom {
.rooms invite_state: sync_events::InviteState {
.room_state_full(&room_id)? events: invite_state_events,
.into_iter() },
.map(|(_, pdu)| pdu.to_stripped_state_event())
.collect(),
}, },
}; );
if !invited_room.is_empty() {
invited_rooms.insert(room_id.clone(), invited_room);
}
} }
for user_id in left_encrypted_users { for user_id in left_encrypted_users {
@ -698,12 +619,7 @@ pub async fn sync_events_route(
if duration.as_secs() > 30 { if duration.as_secs() > 30 {
duration = Duration::from_secs(30); duration = Duration::from_secs(30);
} }
let delay = tokio::time::sleep(duration); let _ = tokio::time::timeout(duration, watcher).await;
tokio::pin!(delay);
tokio::select! {
_ = &mut delay => {}
_ = watcher => {}
}
} }
Ok(response.into()) Ok(response.into())

@ -1,7 +1,6 @@
use crate::ConduitResult; use crate::ConduitResult;
use ruma::api::client::r0::thirdparty::get_protocols; use ruma::api::client::r0::thirdparty::get_protocols;
use log::warn;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::get; use rocket::get;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -12,7 +11,7 @@ use std::collections::BTreeMap;
)] )]
#[tracing::instrument] #[tracing::instrument]
pub async fn get_protocols_route() -> ConduitResult<get_protocols::Response> { pub async fn get_protocols_route() -> ConduitResult<get_protocols::Response> {
warn!("TODO: get_protocols_route"); // TODO
Ok(get_protocols::Response { Ok(get_protocols::Response {
protocols: BTreeMap::new(), protocols: BTreeMap::new(),
} }

@ -4,6 +4,7 @@ pub mod appservice;
pub mod globals; pub mod globals;
pub mod key_backups; pub mod key_backups;
pub mod media; pub mod media;
pub mod pusher;
pub mod rooms; pub mod rooms;
pub mod sending; pub mod sending;
pub mod transaction_ids; pub mod transaction_ids;
@ -17,12 +18,14 @@ use log::info;
use rocket::futures::{self, channel::mpsc}; use rocket::futures::{self, channel::mpsc};
use ruma::{DeviceId, ServerName, UserId}; use ruma::{DeviceId, ServerName, UserId};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::{
use std::fs::remove_dir_all; collections::HashMap,
use std::sync::{Arc, RwLock}; fs::remove_dir_all,
sync::{Arc, RwLock},
};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
#[derive(Clone, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Config { pub struct Config {
server_name: Box<ServerName>, server_name: Box<ServerName>,
database_path: String, database_path: String,
@ -41,6 +44,10 @@ pub struct Config {
#[serde(default = "false_fn")] #[serde(default = "false_fn")]
pub allow_jaeger: bool, pub allow_jaeger: bool,
jwt_secret: Option<String>, jwt_secret: Option<String>,
#[serde(default = "Vec::new")]
trusted_servers: Vec<Box<ServerName>>,
#[serde(default = "default_log")]
pub log: String,
} }
fn false_fn() -> bool { fn false_fn() -> bool {
@ -63,6 +70,10 @@ fn default_max_concurrent_requests() -> u16 {
4 4
} }
fn default_log() -> String {
"info,state_res=warn,rocket=off,_=off,sled=off".to_owned()
}
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {
pub globals: globals::Globals, pub globals: globals::Globals,
@ -76,6 +87,7 @@ pub struct Database {
pub sending: sending::Sending, pub sending: sending::Sending,
pub admin: admin::Admin, pub admin: admin::Admin,
pub appservice: appservice::Appservice, pub appservice: appservice::Appservice,
pub pusher: pusher::PushData,
pub _db: sled::Db, pub _db: sled::Db,
} }
@ -97,6 +109,7 @@ impl Database {
let db = sled::Config::default() let db = sled::Config::default()
.path(&config.database_path) .path(&config.database_path)
.cache_capacity(config.cache_capacity as u64) .cache_capacity(config.cache_capacity as u64)
.use_compression(true)
.open()?; .open()?;
info!("Opened sled database at {}", config.database_path); info!("Opened sled database at {}", config.database_path);
@ -104,7 +117,6 @@ impl Database {
let (admin_sender, admin_receiver) = mpsc::unbounded(); let (admin_sender, admin_receiver) = mpsc::unbounded();
let db = Self { let db = Self {
globals: globals::Globals::load(db.open_tree("global")?, config).await?,
users: users::Users { users: users::Users {
userid_password: db.open_tree("userid_password")?, userid_password: db.open_tree("userid_password")?,
userid_displayname: db.open_tree("userid_displayname")?, userid_displayname: db.open_tree("userid_displayname")?,
@ -114,7 +126,7 @@ impl Database {
token_userdeviceid: db.open_tree("token_userdeviceid")?, token_userdeviceid: db.open_tree("token_userdeviceid")?,
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?, onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?,
userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?, userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?,
keychangeid_userid: db.open_tree("devicekeychangeid_userid")?, keychangeid_userid: db.open_tree("keychangeid_userid")?,
keyid_key: db.open_tree("keyid_key")?, keyid_key: db.open_tree("keyid_key")?,
userid_masterkeyid: db.open_tree("userid_masterkeyid")?, userid_masterkeyid: db.open_tree("userid_masterkeyid")?,
userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?, userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?,
@ -129,7 +141,7 @@ impl Database {
readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?, readreceiptid_readreceipt: db.open_tree("readreceiptid_readreceipt")?,
roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt roomuserid_privateread: db.open_tree("roomuserid_privateread")?, // "Private" read receipt
roomuserid_lastprivatereadupdate: db roomuserid_lastprivatereadupdate: db
.open_tree("roomid_lastprivatereadupdate")?, .open_tree("roomuserid_lastprivatereadupdate")?,
typingid_userid: db.open_tree("typingid_userid")?, typingid_userid: db.open_tree("typingid_userid")?,
roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?, roomid_lasttypingupdate: db.open_tree("roomid_lasttypingupdate")?,
presenceid_presence: db.open_tree("presenceid_presence")?, presenceid_presence: db.open_tree("presenceid_presence")?,
@ -140,7 +152,7 @@ impl Database {
roomid_pduleaves: db.open_tree("roomid_pduleaves")?, roomid_pduleaves: db.open_tree("roomid_pduleaves")?,
alias_roomid: db.open_tree("alias_roomid")?, alias_roomid: db.open_tree("alias_roomid")?,
aliasid_alias: db.open_tree("alias_roomid")?, aliasid_alias: db.open_tree("aliasid_alias")?,
publicroomids: db.open_tree("publicroomids")?, publicroomids: db.open_tree("publicroomids")?,
tokenids: db.open_tree("tokenids")?, tokenids: db.open_tree("tokenids")?,
@ -149,14 +161,24 @@ impl Database {
userroomid_joined: db.open_tree("userroomid_joined")?, userroomid_joined: db.open_tree("userroomid_joined")?,
roomuserid_joined: db.open_tree("roomuserid_joined")?, roomuserid_joined: db.open_tree("roomuserid_joined")?,
roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?, roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?,
userroomid_invited: db.open_tree("userroomid_invited")?, userroomid_invitestate: db.open_tree("userroomid_invitestate")?,
roomuserid_invited: db.open_tree("roomuserid_invited")?, roomuserid_invitecount: db.open_tree("roomuserid_invitecount")?,
userroomid_left: db.open_tree("userroomid_left")?, userroomid_leftstate: db.open_tree("userroomid_leftstate")?,
roomuserid_leftcount: db.open_tree("roomuserid_leftcount")?,
statekey_short: db.open_tree("statekey_short")?,
stateid_pduid: db.open_tree("stateid_pduid")?, userroomid_notificationcount: db.open_tree("userroomid_notificationcount")?,
pduid_statehash: db.open_tree("pduid_statehash")?, userroomid_highlightcount: db.open_tree("userroomid_highlightcount")?,
roomid_statehash: db.open_tree("roomid_statehash")?,
statekey_shortstatekey: db.open_tree("statekey_shortstatekey")?,
stateid_shorteventid: db.open_tree("stateid_shorteventid")?,
eventid_shorteventid: db.open_tree("eventid_shorteventid")?,
shorteventid_eventid: db.open_tree("shorteventid_eventid")?,
shorteventid_shortstatehash: db.open_tree("shorteventid_shortstatehash")?,
roomid_shortstatehash: db.open_tree("roomid_shortstatehash")?,
statehash_shortstatehash: db.open_tree("statehash_shortstatehash")?,
eventid_outlierpdu: db.open_tree("eventid_outlierpdu")?,
prevevent_parent: db.open_tree("prevevent_parent")?,
}, },
account_data: account_data::AccountData { account_data: account_data::AccountData {
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?, roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
@ -167,7 +189,7 @@ impl Database {
key_backups: key_backups::KeyBackups { key_backups: key_backups::KeyBackups {
backupid_algorithm: db.open_tree("backupid_algorithm")?, backupid_algorithm: db.open_tree("backupid_algorithm")?,
backupid_etag: db.open_tree("backupid_etag")?, backupid_etag: db.open_tree("backupid_etag")?,
backupkeyid_backup: db.open_tree("backupkeyid_backupmetadata")?, backupkeyid_backup: db.open_tree("backupkeyid_backup")?,
}, },
transaction_ids: transaction_ids::TransactionIds { transaction_ids: transaction_ids::TransactionIds {
userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?, userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?,
@ -175,7 +197,7 @@ impl Database {
sending: sending::Sending { sending: sending::Sending {
servernamepduids: db.open_tree("servernamepduids")?, servernamepduids: db.open_tree("servernamepduids")?,
servercurrentpdus: db.open_tree("servercurrentpdus")?, servercurrentpdus: db.open_tree("servercurrentpdus")?,
maximum_requests: Arc::new(Semaphore::new(10)), maximum_requests: Arc::new(Semaphore::new(config.max_concurrent_requests as usize)),
}, },
admin: admin::Admin { admin: admin::Admin {
sender: admin_sender, sender: admin_sender,
@ -184,6 +206,12 @@ impl Database {
cached_registrations: Arc::new(RwLock::new(HashMap::new())), cached_registrations: Arc::new(RwLock::new(HashMap::new())),
id_appserviceregistrations: db.open_tree("id_appserviceregistrations")?, id_appserviceregistrations: db.open_tree("id_appserviceregistrations")?,
}, },
pusher: pusher::PushData::new(&db)?,
globals: globals::Globals::load(
db.open_tree("global")?,
db.open_tree("servertimeout_signingkey")?,
config,
)?,
_db: db, _db: db,
}; };
@ -193,7 +221,7 @@ impl Database {
} }
pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) { pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) {
let userid_bytes = user_id.to_string().as_bytes().to_vec(); let userid_bytes = user_id.as_bytes().to_vec();
let mut userid_prefix = userid_bytes.clone(); let mut userid_prefix = userid_bytes.clone();
userid_prefix.push(0xff); userid_prefix.push(0xff);
@ -212,12 +240,16 @@ impl Database {
); );
futures.push(self.rooms.userroomid_joined.watch_prefix(&userid_prefix)); futures.push(self.rooms.userroomid_joined.watch_prefix(&userid_prefix));
futures.push(self.rooms.userroomid_invited.watch_prefix(&userid_prefix)); futures.push(
futures.push(self.rooms.userroomid_left.watch_prefix(&userid_prefix)); self.rooms
.userroomid_invitestate
.watch_prefix(&userid_prefix),
);
futures.push(self.rooms.userroomid_leftstate.watch_prefix(&userid_prefix));
// Events for rooms we are in // Events for rooms we are in
for room_id in self.rooms.rooms_joined(user_id).filter_map(|r| r.ok()) { for room_id in self.rooms.rooms_joined(user_id).filter_map(|r| r.ok()) {
let roomid_bytes = room_id.to_string().as_bytes().to_vec(); let roomid_bytes = room_id.as_bytes().to_vec();
let mut roomid_prefix = roomid_bytes.clone(); let mut roomid_prefix = roomid_bytes.clone();
roomid_prefix.push(0xff); roomid_prefix.push(0xff);
@ -277,7 +309,8 @@ impl Database {
} }
pub async fn flush(&self) -> Result<()> { pub async fn flush(&self) -> Result<()> {
self._db.flush_async().await?; // noop while we don't use sled 1.0
//self._db.flush_async().await?;
Ok(()) Ok(())
} }
} }

@ -30,7 +30,7 @@ impl AccountData {
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(&user_id.to_string().as_bytes()); prefix.extend_from_slice(&user_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
// Remove old entry // Remove old entry
@ -42,7 +42,7 @@ impl AccountData {
let mut key = prefix; let mut key = prefix;
key.extend_from_slice(&globals.next_count()?.to_be_bytes()); key.extend_from_slice(&globals.next_count()?.to_be_bytes());
key.push(0xff); key.push(0xff);
key.extend_from_slice(event_type.to_string().as_bytes()); key.extend_from_slice(event_type.as_ref().as_bytes());
let json = serde_json::to_value(data).expect("all types here can be serialized"); // TODO: maybe add error handling let json = serde_json::to_value(data).expect("all types here can be serialized"); // TODO: maybe add error handling
if json.get("type").is_none() || json.get("content").is_none() { if json.get("type").is_none() || json.get("content").is_none() {
@ -89,7 +89,7 @@ impl AccountData {
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(&user_id.to_string().as_bytes()); prefix.extend_from_slice(&user_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
// Skip the data that's exactly at since, because we sent that last time // Skip the data that's exactly at since, because we sent that last time
@ -135,7 +135,7 @@ impl AccountData {
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(&user_id.to_string().as_bytes()); prefix.extend_from_slice(&user_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
let kind = kind.clone(); let kind = kind.clone();
@ -148,7 +148,7 @@ impl AccountData {
k.rsplit(|&b| b == 0xff) k.rsplit(|&b| b == 0xff)
.next() .next()
.map(|current_event_type| { .map(|current_event_type| {
current_event_type == kind.to_string().as_bytes() current_event_type == kind.as_ref().as_bytes()
}) })
.unwrap_or(false) .unwrap_or(false)
}) })

@ -59,11 +59,7 @@ impl Admin {
}, },
&conduit_user, &conduit_user,
&conduit_room, &conduit_room,
&db.globals, &db,
&db.sending,
&db.admin,
&db.account_data,
&db.appservice,
) )
.unwrap(); .unwrap();
} }

@ -1,6 +1,8 @@
use crate::{utils, Error, Result}; use crate::{utils, Error, Result};
use std::collections::HashMap; use std::{
use std::sync::{Arc, RwLock}; collections::HashMap,
sync::{Arc, RwLock},
};
#[derive(Clone)] #[derive(Clone)]
pub struct Appservice { pub struct Appservice {
@ -53,9 +55,7 @@ impl Appservice {
}) })
} }
pub fn iter_all<'a>( pub fn iter_all(&self) -> impl Iterator<Item = Result<(String, serde_yaml::Value)>> + '_ {
&'a self,
) -> impl Iterator<Item = Result<(String, serde_yaml::Value)>> + 'a {
self.iter_ids().filter_map(|id| id.ok()).map(move |id| { self.iter_ids().filter_map(|id| id.ok()).map(move |id| {
Ok(( Ok((
id.clone(), id.clone(),

@ -1,15 +1,19 @@
use crate::{database::Config, utils, Error, Result}; use crate::{database::Config, utils, Error, Result};
use log::error; use log::error;
use ruma::ServerName; use ruma::{
use std::collections::HashMap; api::federation::discovery::{ServerSigningKeys, VerifyKey},
use std::sync::Arc; ServerName, ServerSigningKeyId,
use std::sync::RwLock; };
use std::time::Duration; use std::{
collections::{BTreeMap, HashMap},
sync::{Arc, RwLock},
time::Duration,
};
use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::TokioAsyncResolver;
pub const COUNTER: &str = "c"; pub const COUNTER: &str = "c";
type WellKnownMap = HashMap<Box<ServerName>, (String, Option<String>)>; type WellKnownMap = HashMap<Box<ServerName>, (String, String)>;
#[derive(Clone)] #[derive(Clone)]
pub struct Globals { pub struct Globals {
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
@ -19,10 +23,15 @@ pub struct Globals {
reqwest_client: reqwest::Client, reqwest_client: reqwest::Client,
dns_resolver: TokioAsyncResolver, dns_resolver: TokioAsyncResolver,
jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>, jwt_decoding_key: Option<jsonwebtoken::DecodingKey<'static>>,
pub(super) servertimeout_signingkey: sled::Tree, // ServerName + Timeout Timestamp -> algorithm:key + pubkey
} }
impl Globals { impl Globals {
pub async fn load(globals: sled::Tree, config: Config) -> Result<Self> { pub fn load(
globals: sled::Tree,
servertimeout_signingkey: sled::Tree,
config: Config,
) -> Result<Self> {
let bytes = &*globals let bytes = &*globals
.update_and_fetch("keypair", utils::generate_keypair)? .update_and_fetch("keypair", utils::generate_keypair)?
.expect("utils::generate_keypair always returns Some"); .expect("utils::generate_keypair always returns Some");
@ -78,6 +87,7 @@ impl Globals {
Error::bad_config("Failed to set up trust dns resolver with system config.") Error::bad_config("Failed to set up trust dns resolver with system config.")
})?, })?,
actual_destination_cache: Arc::new(RwLock::new(HashMap::new())), actual_destination_cache: Arc::new(RwLock::new(HashMap::new())),
servertimeout_signingkey,
jwt_decoding_key, jwt_decoding_key,
}) })
} }
@ -129,6 +139,10 @@ impl Globals {
self.config.allow_federation self.config.allow_federation
} }
pub fn trusted_servers(&self) -> &[Box<ServerName>] {
&self.config.trusted_servers
}
pub fn dns_resolver(&self) -> &TokioAsyncResolver { pub fn dns_resolver(&self) -> &TokioAsyncResolver {
&self.dns_resolver &self.dns_resolver
} }
@ -136,4 +150,64 @@ impl Globals {
pub fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey<'_>> { pub fn jwt_decoding_key(&self) -> Option<&jsonwebtoken::DecodingKey<'_>> {
self.jwt_decoding_key.as_ref() self.jwt_decoding_key.as_ref()
} }
/// TODO: the key valid until timestamp is only honored in room version > 4
/// Remove the outdated keys and insert the new ones.
///
/// This doesn't actually check that the keys provided are newer than the old set.
pub fn add_signing_key(&self, origin: &ServerName, keys: &ServerSigningKeys) -> Result<()> {
let mut key1 = origin.as_bytes().to_vec();
key1.push(0xff);
let mut key2 = key1.clone();
let ts = keys
.valid_until_ts
.duration_since(std::time::UNIX_EPOCH)
.expect("time is valid")
.as_millis() as u64;
key1.extend_from_slice(&ts.to_be_bytes());
key2.extend_from_slice(&(ts + 1).to_be_bytes());
self.servertimeout_signingkey.insert(
key1,
serde_json::to_vec(&keys.verify_keys).expect("ServerSigningKeys are a valid string"),
)?;
self.servertimeout_signingkey.insert(
key2,
serde_json::to_vec(&keys.old_verify_keys)
.expect("ServerSigningKeys are a valid string"),
)?;
Ok(())
}
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found for the server.
pub fn signing_keys_for(
&self,
origin: &ServerName,
) -> Result<BTreeMap<ServerSigningKeyId, VerifyKey>> {
let mut response = BTreeMap::new();
let now = crate::utils::millis_since_unix_epoch();
for item in self.servertimeout_signingkey.scan_prefix(origin.as_bytes()) {
let (k, bytes) = item?;
let valid_until = k
.splitn(2, |&b| b == 0xff)
.nth(1)
.map(crate::utils::u64_from_bytes)
.ok_or_else(|| Error::bad_database("Invalid signing keys."))?
.map_err(|_| Error::bad_database("Invalid signing key valid until bytes"))?;
// If these keys are still valid use em!
if valid_until > now {
let btree: BTreeMap<_, _> = serde_json::from_slice(&bytes)
.map_err(|_| Error::bad_database("Invalid BTreeMap<> of signing keys"))?;
response.extend(btree);
}
}
Ok(response)
}
} }

@ -2,7 +2,7 @@ use crate::{utils, Error, Result};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
r0::backup::{BackupAlgorithm, KeyData, Sessions}, r0::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup},
}, },
RoomId, UserId, RoomId, UserId,
}; };
@ -24,7 +24,7 @@ impl KeyBackups {
) -> Result<String> { ) -> Result<String> {
let version = globals.next_count()?.to_string(); let version = globals.next_count()?.to_string();
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
@ -39,7 +39,7 @@ impl KeyBackups {
} }
pub fn delete_backup(&self, user_id: &UserId, version: &str) -> Result<()> { pub fn delete_backup(&self, user_id: &UserId, version: &str) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
@ -67,7 +67,7 @@ impl KeyBackups {
backup_metadata: &BackupAlgorithm, backup_metadata: &BackupAlgorithm,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<String> { ) -> Result<String> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
@ -89,7 +89,7 @@ impl KeyBackups {
} }
pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> { pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
self.backupid_algorithm self.backupid_algorithm
.scan_prefix(&prefix) .scan_prefix(&prefix)
@ -113,7 +113,7 @@ impl KeyBackups {
} }
pub fn get_backup(&self, user_id: &UserId, version: &str) -> Result<Option<BackupAlgorithm>> { pub fn get_backup(&self, user_id: &UserId, version: &str) -> Result<Option<BackupAlgorithm>> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(version.as_bytes()); key.extend_from_slice(version.as_bytes());
@ -129,10 +129,10 @@ impl KeyBackups {
version: &str, version: &str,
room_id: &RoomId, room_id: &RoomId,
session_id: &str, session_id: &str,
key_data: &KeyData, key_data: &KeyBackupData,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(version.as_bytes()); key.extend_from_slice(version.as_bytes());
@ -147,20 +147,20 @@ impl KeyBackups {
.insert(&key, &globals.next_count()?.to_be_bytes())?; .insert(&key, &globals.next_count()?.to_be_bytes())?;
key.push(0xff); key.push(0xff);
key.extend_from_slice(room_id.to_string().as_bytes()); key.extend_from_slice(room_id.as_bytes());
key.push(0xff); key.push(0xff);
key.extend_from_slice(session_id.as_bytes()); key.extend_from_slice(session_id.as_bytes());
self.backupkeyid_backup.insert( self.backupkeyid_backup.insert(
&key, &key,
&*serde_json::to_string(&key_data).expect("KeyData::to_string always works"), &*serde_json::to_string(&key_data).expect("KeyBackupData::to_string always works"),
)?; )?;
Ok(()) Ok(())
} }
pub fn count_keys(&self, user_id: &UserId, version: &str) -> Result<usize> { pub fn count_keys(&self, user_id: &UserId, version: &str) -> Result<usize> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes()); prefix.extend_from_slice(version.as_bytes());
@ -168,7 +168,7 @@ impl KeyBackups {
} }
pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> { pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
@ -182,13 +182,17 @@ impl KeyBackups {
.to_string()) .to_string())
} }
pub fn get_all(&self, user_id: &UserId, version: &str) -> Result<BTreeMap<RoomId, Sessions>> { pub fn get_all(
let mut prefix = user_id.to_string().as_bytes().to_vec(); &self,
user_id: &UserId,
version: &str,
) -> Result<BTreeMap<RoomId, RoomKeyBackup>> {
let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes()); prefix.extend_from_slice(version.as_bytes());
prefix.push(0xff); prefix.push(0xff);
let mut rooms = BTreeMap::<RoomId, Sessions>::new(); let mut rooms = BTreeMap::<RoomId, RoomKeyBackup>::new();
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| { for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| {
let (key, value) = r?; let (key, value) = r?;
@ -211,15 +215,16 @@ impl KeyBackups {
) )
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?; .map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?;
let key_data = serde_json::from_slice(&value) let key_data = serde_json::from_slice(&value).map_err(|_| {
.map_err(|_| Error::bad_database("KeyData in backupkeyid_backup is invalid."))?; Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
})?;
Ok::<_, Error>((room_id, session_id, key_data)) Ok::<_, Error>((room_id, session_id, key_data))
}) { }) {
let (room_id, session_id, key_data) = result?; let (room_id, session_id, key_data) = result?;
rooms rooms
.entry(room_id) .entry(room_id)
.or_insert_with(|| Sessions { .or_insert_with(|| RoomKeyBackup {
sessions: BTreeMap::new(), sessions: BTreeMap::new(),
}) })
.sessions .sessions
@ -234,8 +239,8 @@ impl KeyBackups {
user_id: &UserId, user_id: &UserId,
version: &str, version: &str,
room_id: &RoomId, room_id: &RoomId,
) -> BTreeMap<String, KeyData> { ) -> BTreeMap<String, KeyBackupData> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(version.as_bytes()); prefix.extend_from_slice(version.as_bytes());
prefix.push(0xff); prefix.push(0xff);
@ -257,7 +262,7 @@ impl KeyBackups {
})?; })?;
let key_data = serde_json::from_slice(&value).map_err(|_| { let key_data = serde_json::from_slice(&value).map_err(|_| {
Error::bad_database("KeyData in backupkeyid_backup is invalid.") Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
})?; })?;
Ok::<_, Error>((session_id, key_data)) Ok::<_, Error>((session_id, key_data))
@ -272,8 +277,8 @@ impl KeyBackups {
version: &str, version: &str,
room_id: &RoomId, room_id: &RoomId,
session_id: &str, session_id: &str,
) -> Result<Option<KeyData>> { ) -> Result<Option<KeyBackupData>> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(version.as_bytes()); key.extend_from_slice(version.as_bytes());
key.push(0xff); key.push(0xff);
@ -284,14 +289,15 @@ impl KeyBackups {
self.backupkeyid_backup self.backupkeyid_backup
.get(&key)? .get(&key)?
.map(|value| { .map(|value| {
serde_json::from_slice(&value) serde_json::from_slice(&value).map_err(|_| {
.map_err(|_| Error::bad_database("KeyData in backupkeyid_backup is invalid.")) Error::bad_database("KeyBackupData in backupkeyid_backup is invalid.")
})
}) })
.transpose() .transpose()
} }
pub fn delete_all_keys(&self, user_id: &UserId, version: &str) -> Result<()> { pub fn delete_all_keys(&self, user_id: &UserId, version: &str) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
key.push(0xff); key.push(0xff);
@ -314,7 +320,7 @@ impl KeyBackups {
version: &str, version: &str,
room_id: &RoomId, room_id: &RoomId,
) -> Result<()> { ) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
key.push(0xff); key.push(0xff);
@ -340,7 +346,7 @@ impl KeyBackups {
room_id: &RoomId, room_id: &RoomId,
session_id: &str, session_id: &str,
) -> Result<()> { ) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&version.as_bytes()); key.extend_from_slice(&version.as_bytes());
key.push(0xff); key.push(0xff);

@ -226,16 +226,17 @@ impl Media {
} }
let thumbnail = if crop { let thumbnail = if crop {
image.resize_to_fill(width, height, FilterType::Triangle) image.resize_to_fill(width, height, FilterType::CatmullRom)
} else { } else {
let (exact_width, exact_height) = { let (exact_width, exact_height) = {
// Copied from image::dynimage::resize_dimensions // Copied from image::dynimage::resize_dimensions
let ratio = u64::from(original_width) * u64::from(height); let ratio = u64::from(original_width) * u64::from(height);
let nratio = u64::from(width) * u64::from(original_height); let nratio = u64::from(width) * u64::from(original_height);
let use_width = nratio > ratio; let use_width = nratio <= ratio;
let intermediate = if use_width { let intermediate = if use_width {
u64::from(original_height) * u64::from(width) / u64::from(width) u64::from(original_height) * u64::from(width)
/ u64::from(original_width)
} else { } else {
u64::from(original_width) * u64::from(height) u64::from(original_width) * u64::from(height)
/ u64::from(original_height) / u64::from(original_height)

@ -0,0 +1,327 @@
use crate::{Database, Error, PduEvent, Result};
use log::{error, info, warn};
use ruma::{
api::{
client::r0::push::{Pusher, PusherKind},
push_gateway::send_event_notification::{
self,
v1::{Device, Notification, NotificationCounts, NotificationPriority},
},
IncomingResponse, OutgoingRequest,
},
events::{room::power_levels::PowerLevelsEventContent, EventType},
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
uint, UInt, UserId,
};
use sled::IVec;
use std::{convert::TryFrom, fmt::Debug};
#[derive(Debug, Clone)]
pub struct PushData {
/// UserId + pushkey -> Pusher
pub(super) senderkey_pusher: sled::Tree,
}
impl PushData {
pub fn new(db: &sled::Db) -> Result<Self> {
Ok(Self {
senderkey_pusher: db.open_tree("senderkey_pusher")?,
})
}
pub fn set_pusher(&self, sender: &UserId, pusher: Pusher) -> Result<()> {
let mut key = sender.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(pusher.pushkey.as_bytes());
// There are 2 kinds of pushers but the spec says: null deletes the pusher.
if pusher.kind.is_none() {
return self
.senderkey_pusher
.remove(key)
.map(|_| ())
.map_err(Into::into);
}
self.senderkey_pusher.insert(
key,
&*serde_json::to_string(&pusher).expect("Pusher is valid JSON string"),
)?;
Ok(())
}
pub fn get_pusher(&self, senderkey: &[u8]) -> Result<Option<Pusher>> {
self.senderkey_pusher
.get(senderkey)?
.map(|push| {
Ok(serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
})
.transpose()
}
pub fn get_pushers(&self, sender: &UserId) -> Result<Vec<Pusher>> {
let mut prefix = sender.as_bytes().to_vec();
prefix.push(0xff);
self.senderkey_pusher
.scan_prefix(prefix)
.values()
.map(|push| {
let push = push.map_err(|_| Error::bad_database("Invalid push bytes in db."))?;
Ok(serde_json::from_slice(&*push)
.map_err(|_| Error::bad_database("Invalid Pusher in db."))?)
})
.collect()
}
pub fn get_pusher_senderkeys(&self, sender: &UserId) -> impl Iterator<Item = Result<IVec>> {
let mut prefix = sender.as_bytes().to_vec();
prefix.push(0xff);
self.senderkey_pusher
.scan_prefix(prefix)
.keys()
.map(|r| Ok(r?))
}
}
pub async fn send_request<T: OutgoingRequest>(
globals: &crate::database::globals::Globals,
destination: &str,
request: T,
) -> Result<T::IncomingResponse>
where
T: Debug,
{
let destination = destination.replace("/_matrix/push/v1/notify", "");
let http_request = request
.try_into_http_request(&destination, Some(""))
.map_err(|e| {
warn!("Failed to find destination {}: {}", destination, e);
Error::BadServerResponse("Invalid destination")
})?;
let reqwest_request = reqwest::Request::try_from(http_request)
.expect("all http requests are valid reqwest requests");
// TODO: we could keep this very short and let expo backoff do it's thing...
//*reqwest_request.timeout_mut() = Some(Duration::from_secs(5));
let url = reqwest_request.url().clone();
let reqwest_response = globals.reqwest_client().execute(reqwest_request).await;
// Because reqwest::Response -> http::Response is complicated:
match reqwest_response {
Ok(mut reqwest_response) => {
let status = reqwest_response.status();
let mut http_response = http::Response::builder().status(status);
let headers = http_response.headers_mut().unwrap();
for (k, v) in reqwest_response.headers_mut().drain() {
if let Some(key) = k {
headers.insert(key, v);
}
}
let status = reqwest_response.status();
let body = reqwest_response.bytes().await.unwrap_or_else(|e| {
warn!("server error {}", e);
Vec::new().into()
}); // TODO: handle timeout
if status != 200 {
info!(
"Push gateway returned bad response {} {}\n{}\n{:?}",
destination,
status,
url,
crate::utils::string_from_bytes(&body)
);
}
let response = T::IncomingResponse::try_from_http_response(
http_response
.body(body)
.expect("reqwest body is valid http body"),
);
response.map_err(|_| {
info!(
"Push gateway returned invalid response bytes {}\n{}",
destination, url
);
Error::BadServerResponse("Push gateway returned bad response.")
})
}
Err(e) => Err(e.into()),
}
}
pub async fn send_push_notice(
user: &UserId,
unread: UInt,
pusher: &Pusher,
ruleset: Ruleset,
pdu: &PduEvent,
db: &Database,
) -> Result<()> {
let mut notify = None;
let mut tweaks = Vec::new();
for action in get_actions(user, &ruleset, pdu, db)? {
let n = match action {
Action::DontNotify => false,
// TODO: Implement proper support for coalesce
Action::Notify | Action::Coalesce => true,
Action::SetTweak(tweak) => {
tweaks.push(tweak.clone());
continue;
}
};
if notify.is_some() {
return Err(Error::bad_database(
r#"Malformed pushrule contains more than one of these actions: ["dont_notify", "notify", "coalesce"]"#,
));
}
notify = Some(n);
}
if notify == Some(true) {
send_notice(unread, pusher, tweaks, pdu, db).await?;
}
// Else the event triggered no actions
Ok(())
}
pub fn get_actions<'a>(
user: &UserId,
ruleset: &'a Ruleset,
pdu: &PduEvent,
db: &Database,
) -> Result<impl 'a + Iterator<Item = Action>> {
let power_levels: PowerLevelsEventContent = db
.rooms
.room_state_get(&pdu.room_id, &EventType::RoomPowerLevels, "")?
.map(|ev| {
serde_json::from_value(ev.content)
.map_err(|_| Error::bad_database("invalid m.room.power_levels event"))
})
.transpose()?
.unwrap_or_default();
let ctx = PushConditionRoomCtx {
room_id: pdu.room_id.clone(),
member_count: 10_u32.into(), // TODO: get member count efficiently
user_display_name: db
.users
.displayname(&user)?
.unwrap_or_else(|| user.localpart().to_owned()),
users_power_levels: power_levels.users,
default_power_level: power_levels.users_default,
notification_power_levels: power_levels.notifications,
};
Ok(ruleset
.get_actions(&pdu.to_sync_room_event(), &ctx)
.map(Clone::clone))
}
async fn send_notice(
unread: UInt,
pusher: &Pusher,
tweaks: Vec<Tweak>,
event: &PduEvent,
db: &Database,
) -> Result<()> {
// TODO: email
if pusher.kind == Some(PusherKind::Email) {
return Ok(());
}
// TODO:
// Two problems with this
// 1. if "event_id_only" is the only format kind it seems we should never add more info
// 2. can pusher/devices have conflicting formats
let event_id_only = pusher.data.format == Some(PushFormat::EventIdOnly);
let url = if let Some(url) = pusher.data.url.as_ref() {
url
} else {
error!("Http Pusher must have URL specified.");
return Ok(());
};
let mut device = Device::new(pusher.app_id.clone(), pusher.pushkey.clone());
let mut data_minus_url = pusher.data.clone();
// The url must be stripped off according to spec
data_minus_url.url = None;
device.data = Some(data_minus_url);
// Tweaks are only added if the format is NOT event_id_only
if !event_id_only {
device.tweaks = tweaks.clone();
}
let d = &[device];
let mut notifi = Notification::new(d);
notifi.prio = NotificationPriority::Low;
notifi.event_id = Some(&event.event_id);
notifi.room_id = Some(&event.room_id);
// TODO: missed calls
notifi.counts = NotificationCounts::new(unread, uint!(0));
if event.kind == EventType::RoomEncrypted
|| tweaks
.iter()
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
{
notifi.prio = NotificationPriority::High
}
if event_id_only {
send_request(
&db.globals,
&url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
} else {
notifi.sender = Some(&event.sender);
notifi.event_type = Some(&event.kind);
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
if event.kind == EventType::RoomMember {
notifi.user_is_target = event.state_key.as_deref() == Some(event.sender.as_str());
}
let user_name = db.users.displayname(&event.sender)?;
notifi.sender_display_name = user_name.as_deref();
let room_name = db
.rooms
.room_state_get(&event.room_id, &EventType::RoomName, "")?
.map(|pdu| match pdu.content.get("name") {
Some(serde_json::Value::String(s)) => Some(s.to_string()),
_ => None,
})
.flatten();
notifi.room_name = room_name.as_deref();
send_request(
&db.globals,
&url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
}
// TODO: email
Ok(())
}

File diff suppressed because it is too large Load Diff

@ -34,7 +34,7 @@ impl RoomEdus {
event: EduEvent, event: EduEvent,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
// Remove old entry // Remove old entry
@ -49,7 +49,7 @@ impl RoomEdus {
key.rsplit(|&b| b == 0xff) key.rsplit(|&b| b == 0xff)
.next() .next()
.expect("rsplit always returns an element") .expect("rsplit always returns an element")
== user_id.to_string().as_bytes() == user_id.as_bytes()
}) })
{ {
// This is the old room_latest // This is the old room_latest
@ -59,7 +59,7 @@ impl RoomEdus {
let mut room_latest_id = prefix; let mut room_latest_id = prefix;
room_latest_id.extend_from_slice(&globals.next_count()?.to_be_bytes()); room_latest_id.extend_from_slice(&globals.next_count()?.to_be_bytes());
room_latest_id.push(0xff); room_latest_id.push(0xff);
room_latest_id.extend_from_slice(&user_id.to_string().as_bytes()); room_latest_id.extend_from_slice(&user_id.as_bytes());
self.readreceiptid_readreceipt.insert( self.readreceiptid_readreceipt.insert(
room_latest_id, room_latest_id,
@ -76,7 +76,7 @@ impl RoomEdus {
room_id: &RoomId, room_id: &RoomId,
since: u64, since: u64,
) -> Result<impl Iterator<Item = Result<Raw<ruma::events::AnySyncEphemeralRoomEvent>>>> { ) -> Result<impl Iterator<Item = Result<Raw<ruma::events::AnySyncEphemeralRoomEvent>>>> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut first_possible_edu = prefix.clone(); let mut first_possible_edu = prefix.clone();
@ -102,9 +102,9 @@ impl RoomEdus {
count: u64, count: u64,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut key = room_id.to_string().as_bytes().to_vec(); let mut key = room_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&user_id.to_string().as_bytes()); key.extend_from_slice(&user_id.as_bytes());
self.roomuserid_privateread self.roomuserid_privateread
.insert(&key, &count.to_be_bytes())?; .insert(&key, &count.to_be_bytes())?;
@ -118,9 +118,9 @@ impl RoomEdus {
/// Returns the private read marker. /// Returns the private read marker.
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn private_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>> { pub fn private_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>> {
let mut key = room_id.to_string().as_bytes().to_vec(); let mut key = room_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&user_id.to_string().as_bytes()); key.extend_from_slice(&user_id.as_bytes());
self.roomuserid_privateread.get(key)?.map_or(Ok(None), |v| { self.roomuserid_privateread.get(key)?.map_or(Ok(None), |v| {
Ok(Some(utils::u64_from_bytes(&v).map_err(|_| { Ok(Some(utils::u64_from_bytes(&v).map_err(|_| {
@ -131,9 +131,9 @@ impl RoomEdus {
/// Returns the count of the last typing update in this room. /// Returns the count of the last typing update in this room.
pub fn last_privateread_update(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64> { pub fn last_privateread_update(&self, user_id: &UserId, room_id: &RoomId) -> Result<u64> {
let mut key = room_id.to_string().as_bytes().to_vec(); let mut key = room_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&user_id.to_string().as_bytes()); key.extend_from_slice(&user_id.as_bytes());
Ok(self Ok(self
.roomuserid_lastprivatereadupdate .roomuserid_lastprivatereadupdate
@ -155,7 +155,7 @@ impl RoomEdus {
timeout: u64, timeout: u64,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let count = globals.next_count()?.to_be_bytes(); let count = globals.next_count()?.to_be_bytes();
@ -166,10 +166,10 @@ impl RoomEdus {
room_typing_id.extend_from_slice(&count); room_typing_id.extend_from_slice(&count);
self.typingid_userid self.typingid_userid
.insert(&room_typing_id, &*user_id.to_string().as_bytes())?; .insert(&room_typing_id, &*user_id.as_bytes())?;
self.roomid_lasttypingupdate self.roomid_lasttypingupdate
.insert(&room_id.to_string().as_bytes(), &count)?; .insert(&room_id.as_bytes(), &count)?;
Ok(()) Ok(())
} }
@ -181,7 +181,7 @@ impl RoomEdus {
room_id: &RoomId, room_id: &RoomId,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let user_id = user_id.to_string(); let user_id = user_id.to_string();
@ -200,10 +200,8 @@ impl RoomEdus {
} }
if found_outdated { if found_outdated {
self.roomid_lasttypingupdate.insert( self.roomid_lasttypingupdate
&room_id.to_string().as_bytes(), .insert(&room_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
&globals.next_count()?.to_be_bytes(),
)?;
} }
Ok(()) Ok(())
@ -215,7 +213,7 @@ impl RoomEdus {
room_id: &RoomId, room_id: &RoomId,
globals: &super::super::globals::Globals, globals: &super::super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let current_timestamp = utils::millis_since_unix_epoch(); let current_timestamp = utils::millis_since_unix_epoch();
@ -248,10 +246,8 @@ impl RoomEdus {
} }
if found_outdated { if found_outdated {
self.roomid_lasttypingupdate.insert( self.roomid_lasttypingupdate
&room_id.to_string().as_bytes(), .insert(&room_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
&globals.next_count()?.to_be_bytes(),
)?;
} }
Ok(()) Ok(())
@ -268,7 +264,7 @@ impl RoomEdus {
Ok(self Ok(self
.roomid_lasttypingupdate .roomid_lasttypingupdate
.get(&room_id.to_string().as_bytes())? .get(&room_id.as_bytes())?
.map_or(Ok::<_, Error>(None), |bytes| { .map_or(Ok::<_, Error>(None), |bytes| {
Ok(Some(utils::u64_from_bytes(&bytes).map_err(|_| { Ok(Some(utils::u64_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.") Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.")
@ -281,7 +277,7 @@ impl RoomEdus {
&self, &self,
room_id: &RoomId, room_id: &RoomId,
) -> Result<SyncEphemeralRoomEvent<ruma::events::typing::TypingEventContent>> { ) -> Result<SyncEphemeralRoomEvent<ruma::events::typing::TypingEventContent>> {
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut user_ids = Vec::new(); let mut user_ids = Vec::new();
@ -322,11 +318,11 @@ impl RoomEdus {
let count = globals.next_count()?.to_be_bytes(); let count = globals.next_count()?.to_be_bytes();
let mut presence_id = room_id.to_string().as_bytes().to_vec(); let mut presence_id = room_id.as_bytes().to_vec();
presence_id.push(0xff); presence_id.push(0xff);
presence_id.extend_from_slice(&count); presence_id.extend_from_slice(&count);
presence_id.push(0xff); presence_id.push(0xff);
presence_id.extend_from_slice(&presence.sender.to_string().as_bytes()); presence_id.extend_from_slice(&presence.sender.as_bytes());
self.presenceid_presence.insert( self.presenceid_presence.insert(
presence_id, presence_id,
@ -334,7 +330,7 @@ impl RoomEdus {
)?; )?;
self.userid_lastpresenceupdate.insert( self.userid_lastpresenceupdate.insert(
&user_id.to_string().as_bytes(), &user_id.as_bytes(),
&utils::millis_since_unix_epoch().to_be_bytes(), &utils::millis_since_unix_epoch().to_be_bytes(),
)?; )?;
@ -345,7 +341,7 @@ impl RoomEdus {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn ping_presence(&self, user_id: &UserId) -> Result<()> { pub fn ping_presence(&self, user_id: &UserId) -> Result<()> {
self.userid_lastpresenceupdate.insert( self.userid_lastpresenceupdate.insert(
&user_id.to_string().as_bytes(), &user_id.as_bytes(),
&utils::millis_since_unix_epoch().to_be_bytes(), &utils::millis_since_unix_epoch().to_be_bytes(),
)?; )?;
@ -355,7 +351,7 @@ impl RoomEdus {
/// Returns the timestamp of the last presence update of this user in millis since the unix epoch. /// Returns the timestamp of the last presence update of this user in millis since the unix epoch.
pub fn last_presence_update(&self, user_id: &UserId) -> Result<Option<u64>> { pub fn last_presence_update(&self, user_id: &UserId) -> Result<Option<u64>> {
self.userid_lastpresenceupdate self.userid_lastpresenceupdate
.get(&user_id.to_string().as_bytes())? .get(&user_id.as_bytes())?
.map(|bytes| { .map(|bytes| {
utils::u64_from_bytes(&bytes).map_err(|_| { utils::u64_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Invalid timestamp in userid_lastpresenceupdate.") Error::bad_database("Invalid timestamp in userid_lastpresenceupdate.")
@ -386,7 +382,7 @@ impl RoomEdus {
.ok()?, .ok()?,
)) ))
}) })
.take_while(|(_, timestamp)| current_timestamp - timestamp > 5 * 60_000) .take_while(|(_, timestamp)| current_timestamp.saturating_sub(*timestamp) > 5 * 60_000)
// 5 Minutes // 5 Minutes
{ {
// Send new presence events to set the user offline // Send new presence events to set the user offline
@ -398,7 +394,7 @@ impl RoomEdus {
.try_into() .try_into()
.map_err(|_| Error::bad_database("Invalid UserId in userid_lastpresenceupdate."))?; .map_err(|_| Error::bad_database("Invalid UserId in userid_lastpresenceupdate."))?;
for room_id in rooms.rooms_joined(&user_id).filter_map(|r| r.ok()) { for room_id in rooms.rooms_joined(&user_id).filter_map(|r| r.ok()) {
let mut presence_id = room_id.to_string().as_bytes().to_vec(); let mut presence_id = room_id.as_bytes().to_vec();
presence_id.push(0xff); presence_id.push(0xff);
presence_id.extend_from_slice(&count); presence_id.extend_from_slice(&count);
presence_id.push(0xff); presence_id.push(0xff);
@ -424,7 +420,7 @@ impl RoomEdus {
} }
self.userid_lastpresenceupdate.insert( self.userid_lastpresenceupdate.insert(
&user_id.to_string().as_bytes(), &user_id.as_bytes(),
&utils::millis_since_unix_epoch().to_be_bytes(), &utils::millis_since_unix_epoch().to_be_bytes(),
)?; )?;
} }
@ -443,7 +439,7 @@ impl RoomEdus {
) -> Result<HashMap<UserId, PresenceEvent>> { ) -> Result<HashMap<UserId, PresenceEvent>> {
self.presence_maintain(rooms, globals)?; self.presence_maintain(rooms, globals)?;
let mut prefix = room_id.to_string().as_bytes().to_vec(); let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
let mut first_possible_edu = prefix.clone(); let mut first_possible_edu = prefix.clone();

@ -1,56 +1,62 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
convert::TryFrom, convert::{TryFrom, TryInto},
fmt::Debug, fmt::Debug,
sync::Arc, sync::Arc,
time::{Duration, Instant, SystemTime}, time::{Duration, Instant, SystemTime},
}; };
use crate::{appservice_server, server_server, utils, Error, PduEvent, Result}; use crate::{
appservice_server, database::pusher, server_server, utils, Database, Error, PduEvent, Result,
};
use federation::transactions::send_transaction_message; use federation::transactions::send_transaction_message;
use log::{info, warn}; use log::warn;
use ring::digest; use ring::digest;
use rocket::futures::stream::{FuturesUnordered, StreamExt}; use rocket::futures::stream::{FuturesUnordered, StreamExt};
use ruma::{ use ruma::{
api::{appservice, federation, OutgoingRequest}, api::{appservice, federation, OutgoingRequest},
ServerName, events::{push_rules, EventType},
push, ServerName, UInt, UserId,
}; };
use sled::IVec; use sled::IVec;
use tokio::select; use tokio::{select, sync::Semaphore};
use tokio::sync::Semaphore;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OutgoingKind {
Appservice(Box<ServerName>),
Push(Vec<u8>, Vec<u8>), // user and pushkey
Normal(Box<ServerName>),
}
#[derive(Clone)] #[derive(Clone)]
pub struct Sending { pub struct Sending {
/// The state for a given state hash. /// The state for a given state hash.
pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+)ServerName + PduId pub(super) servernamepduids: sled::Tree, // ServernamePduId = (+ / $)SenderKey / ServerName / UserId + PduId
pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+)ServerName + PduId (pduid can be empty for reservation) pub(super) servercurrentpdus: sled::Tree, // ServerCurrentPdus = (+ / $)ServerName / UserId + PduId (pduid can be empty for reservation)
pub(super) maximum_requests: Arc<Semaphore>, pub(super) maximum_requests: Arc<Semaphore>,
} }
impl Sending { impl Sending {
pub fn start_handler( pub fn start_handler(&self, db: &Database) {
&self,
globals: &super::globals::Globals,
rooms: &super::rooms::Rooms,
appservice: &super::appservice::Appservice,
) {
let servernamepduids = self.servernamepduids.clone(); let servernamepduids = self.servernamepduids.clone();
let servercurrentpdus = self.servercurrentpdus.clone(); let servercurrentpdus = self.servercurrentpdus.clone();
let maximum_requests = self.maximum_requests.clone();
let rooms = rooms.clone(); let db = db.clone();
let globals = globals.clone();
let appservice = appservice.clone();
tokio::spawn(async move { tokio::spawn(async move {
let mut futures = FuturesUnordered::new(); let mut futures = FuturesUnordered::new();
// Retry requests we could not finish yet // Retry requests we could not finish yet
let mut current_transactions = HashMap::<(Box<ServerName>, bool), Vec<IVec>>::new(); let mut current_transactions = HashMap::<OutgoingKind, Vec<Vec<u8>>>::new();
for (key, server, pdu, is_appservice) in servercurrentpdus for (key, outgoing_kind, pdu) in servercurrentpdus
.iter() .iter()
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.filter_map(|(key, _)| Self::parse_servercurrentpdus(key).ok()) .filter_map(|(key, _)| {
Self::parse_servercurrentpdus(&key)
.ok()
.map(|(k, p)| (key, k, p.to_vec()))
})
{ {
if pdu.is_empty() { if pdu.is_empty() {
// Remove old reservation key // Remove old reservation key
@ -59,7 +65,7 @@ impl Sending {
} }
let entry = current_transactions let entry = current_transactions
.entry((server, is_appservice)) .entry(outgoing_kind)
.or_insert_with(Vec::new); .or_insert_with(Vec::new);
if entry.len() > 30 { if entry.len() > 30 {
@ -71,42 +77,60 @@ impl Sending {
entry.push(pdu); entry.push(pdu);
} }
for ((server, is_appservice), pdus) in current_transactions { for (outgoing_kind, pdus) in current_transactions {
// Create new reservation // Create new reservation
let mut prefix = if is_appservice { let mut prefix = match &outgoing_kind {
b"+".to_vec() OutgoingKind::Appservice(server) => {
} else { let mut p = b"+".to_vec();
Vec::new() p.extend_from_slice(server.as_bytes());
p
}
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
}
OutgoingKind::Normal(server) => {
let mut p = Vec::new();
p.extend_from_slice(server.as_bytes());
p
}
}; };
prefix.extend_from_slice(server.as_bytes());
prefix.push(0xff); prefix.push(0xff);
servercurrentpdus.insert(prefix, &[]).unwrap(); servercurrentpdus.insert(prefix, &[]).unwrap();
futures.push(Self::handle_event( futures.push(Self::handle_event(outgoing_kind.clone(), pdus, &db));
server,
is_appservice,
pdus,
&globals,
&rooms,
&appservice,
&maximum_requests,
));
} }
let mut last_failed_try: HashMap<Box<ServerName>, (u32, Instant)> = HashMap::new(); let mut last_failed_try: HashMap<OutgoingKind, (u32, Instant)> = HashMap::new();
let mut subscriber = servernamepduids.watch_prefix(b""); let mut subscriber = servernamepduids.watch_prefix(b"");
loop { loop {
select! { select! {
Some(response) = futures.next() => { Some(response) = futures.next() => {
match response { match response {
Ok((server, is_appservice)) => { Ok(outgoing_kind) => {
let mut prefix = if is_appservice { let mut prefix = match &outgoing_kind {
b"+".to_vec() OutgoingKind::Appservice(server) => {
} else { let mut p = b"+".to_vec();
Vec::new() p.extend_from_slice(server.as_bytes());
p
}
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(server) => {
let mut p = vec![];
p.extend_from_slice(server.as_bytes());
p
},
}; };
prefix.extend_from_slice(server.as_bytes());
prefix.push(0xff); prefix.push(0xff);
for key in servercurrentpdus for key in servercurrentpdus
@ -126,7 +150,7 @@ impl Sending {
.keys() .keys()
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.map(|k| { .map(|k| {
k.subslice(prefix.len(), k.len() - prefix.len()) k[prefix.len()..].to_vec()
}) })
.take(30) .take(30)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -139,23 +163,42 @@ impl Sending {
servernamepduids.remove(&current_key).unwrap(); servernamepduids.remove(&current_key).unwrap();
} }
futures.push(Self::handle_event(server, is_appservice, new_pdus, &globals, &rooms, &appservice, &maximum_requests)); futures.push(
Self::handle_event(
outgoing_kind.clone(),
new_pdus,
&db,
)
);
} else { } else {
servercurrentpdus.remove(&prefix).unwrap(); servercurrentpdus.remove(&prefix).unwrap();
// servercurrentpdus with the prefix should be empty now // servercurrentpdus with the prefix should be empty now
} }
} }
Err((server, is_appservice, e)) => { Err((outgoing_kind, _)) => {
info!("Couldn't send transaction to {}\n{}", server, e); let mut prefix = match &outgoing_kind {
let mut prefix = if is_appservice { OutgoingKind::Appservice(serv) => {
b"+".to_vec() let mut p = b"+".to_vec();
} else { p.extend_from_slice(serv.as_bytes());
Vec::new() p
},
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(serv) => {
let mut p = vec![];
p.extend_from_slice(serv.as_bytes());
p
},
}; };
prefix.extend_from_slice(server.as_bytes());
prefix.push(0xff); prefix.push(0xff);
last_failed_try.insert(server.clone(), match last_failed_try.get(&server) { last_failed_try.insert(outgoing_kind.clone(), match last_failed_try.get(&outgoing_kind) {
Some(last_failed) => { Some(last_failed) => {
(last_failed.0+1, Instant::now()) (last_failed.0+1, Instant::now())
}, },
@ -169,52 +212,50 @@ impl Sending {
}, },
Some(event) = &mut subscriber => { Some(event) = &mut subscriber => {
if let sled::Event::Insert { key, .. } = event { if let sled::Event::Insert { key, .. } = event {
// New sled version:
//for (_tree, key, value_opt) in &event {
// if value_opt.is_none() {
// continue;
// }
let servernamepduid = key.clone(); let servernamepduid = key.clone();
let mut parts = servernamepduid.splitn(2, |&b| b == 0xff);
if let Some((server, is_appservice, pdu_id)) = utils::string_from_bytes( let exponential_backoff = |(tries, instant): &(u32, Instant)| {
parts // Fail if a request has failed recently (exponential backoff)
.next() let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
.expect("splitn will always return 1 or more elements"), if min_elapsed_duration > Duration::from_secs(60*60*24) {
) min_elapsed_duration = Duration::from_secs(60*60*24);
.map_err(|_| Error::bad_database("ServerName in servernamepduid bytes are invalid.")) }
.map(|server_str| {
// Appservices start with a plus instant.elapsed() < min_elapsed_duration
if server_str.starts_with('+') { };
(server_str[1..].to_owned(), true)
} else { if let Some((outgoing_kind, pdu_id)) = Self::parse_servercurrentpdus(&servernamepduid)
(server_str, false)
}
})
.and_then(|(server_str, is_appservice)| Box::<ServerName>::try_from(server_str)
.map_err(|_| Error::bad_database("ServerName in servernamepduid is invalid.")).map(|s| (s, is_appservice)))
.ok() .ok()
.and_then(|(server, is_appservice)| parts .filter(|(outgoing_kind, _)| {
.next() if last_failed_try.get(outgoing_kind).map_or(false, exponential_backoff) {
.ok_or_else(|| Error::bad_database("Invalid servernamepduid in db."))
.ok()
.map(|pdu_id| (server, is_appservice, pdu_id))
)
.filter(|(server, is_appservice, _)| {
#[allow(clippy::blocks_in_if_conditions)]
if last_failed_try.get(server).map_or(false, |(tries, instant)| {
// Fail if a request has failed recently (exponential backoff)
let mut min_elapsed_duration = Duration::from_secs(60) * *tries * *tries;
if min_elapsed_duration > Duration::from_secs(60*60*24) {
min_elapsed_duration = Duration::from_secs(60*60*24);
}
instant.elapsed() < min_elapsed_duration
}) {
return false; return false;
} }
let mut prefix = if *is_appservice { let mut prefix = match outgoing_kind {
b"+".to_vec() OutgoingKind::Appservice(serv) => {
} else { let mut p = b"+".to_vec();
Vec::new() p.extend_from_slice(serv.as_bytes());
p
},
OutgoingKind::Push(user, pushkey) => {
let mut p = b"$".to_vec();
p.extend_from_slice(&user);
p.push(0xff);
p.extend_from_slice(&pushkey);
p
},
OutgoingKind::Normal(serv) => {
let mut p = vec![];
p.extend_from_slice(serv.as_bytes());
p
},
}; };
prefix.extend_from_slice(server.as_bytes());
prefix.push(0xff); prefix.push(0xff);
servercurrentpdus servercurrentpdus
@ -225,7 +266,15 @@ impl Sending {
servercurrentpdus.insert(&key, &[]).unwrap(); servercurrentpdus.insert(&key, &[]).unwrap();
servernamepduids.remove(&key).unwrap(); servernamepduids.remove(&key).unwrap();
futures.push(Self::handle_event(server, is_appservice, vec![pdu_id.into()], &globals, &rooms, &appservice, &maximum_requests)); last_failed_try.remove(&outgoing_kind);
futures.push(
Self::handle_event(
outgoing_kind,
vec![pdu_id.to_vec()],
&db,
)
);
} }
} }
} }
@ -234,6 +283,17 @@ impl Sending {
}); });
} }
#[tracing::instrument(skip(self))]
pub fn send_push_pdu(&self, pdu_id: &[u8], senderkey: IVec) -> Result<()> {
let mut key = b"$".to_vec();
key.extend_from_slice(&senderkey);
key.push(0xff);
key.extend_from_slice(pdu_id);
self.servernamepduids.insert(key, b"")?;
Ok(())
}
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn send_pdu(&self, server: &ServerName, pdu_id: &[u8]) -> Result<()> { pub fn send_pdu(&self, server: &ServerName, pdu_id: &[u8]) -> Result<()> {
let mut key = server.as_bytes().to_vec(); let mut key = server.as_bytes().to_vec();
@ -256,155 +316,272 @@ impl Sending {
} }
#[tracing::instrument] #[tracing::instrument]
fn calculate_hash(keys: &[IVec]) -> Vec<u8> { fn calculate_hash(keys: &[Vec<u8>]) -> Vec<u8> {
// We only hash the pdu's event ids, not the whole pdu // We only hash the pdu's event ids, not the whole pdu
let bytes = keys.join(&0xff); let bytes = keys.join(&0xff);
let hash = digest::digest(&digest::SHA256, &bytes); let hash = digest::digest(&digest::SHA256, &bytes);
hash.as_ref().to_owned() hash.as_ref().to_owned()
} }
#[tracing::instrument(skip(globals, rooms, appservice))] #[tracing::instrument(skip(db))]
async fn handle_event( async fn handle_event(
server: Box<ServerName>, kind: OutgoingKind,
is_appservice: bool, pdu_ids: Vec<Vec<u8>>,
pdu_ids: Vec<IVec>, db: &Database,
globals: &super::globals::Globals, ) -> std::result::Result<OutgoingKind, (OutgoingKind, Error)> {
rooms: &super::rooms::Rooms, match &kind {
appservice: &super::appservice::Appservice, OutgoingKind::Appservice(server) => {
maximum_requests: &Semaphore, let pdu_jsons = pdu_ids
) -> std::result::Result<(Box<ServerName>, bool), (Box<ServerName>, bool, Error)> { .iter()
if is_appservice { .map(|pdu_id| {
let pdu_jsons = pdu_ids Ok::<_, (Box<ServerName>, Error)>(
.iter() db.rooms
.map(|pdu_id| { .get_pdu_from_id(pdu_id)
Ok::<_, (Box<ServerName>, Error)>( .map_err(|e| (server.clone(), e))?
rooms .ok_or_else(|| {
.get_pdu_from_id(pdu_id) (
.map_err(|e| (server.clone(), e))? server.clone(),
.ok_or_else(|| { Error::bad_database(
( "[Appservice] Event in servernamepduids not found in ",
server.clone(), ),
Error::bad_database( )
"Event in servernamepduids not found in db.", })?
), .to_any_event(),
) )
})? })
.to_any_event(), .filter_map(|r| r.ok())
) .collect::<Vec<_>>();
}) let permit = db.sending.maximum_requests.acquire().await;
.filter_map(|r| r.ok())
.collect::<Vec<_>>(); let response = appservice_server::send_request(
&db.globals,
let permit = maximum_requests.acquire().await; db.appservice
let response = appservice_server::send_request( .get_registration(server.as_str())
&globals, .unwrap()
appservice .unwrap(), // TODO: handle error
.get_registration(server.as_str()) appservice::event::push_events::v1::Request {
.unwrap() events: &pdu_jsons,
.unwrap(), // TODO: handle error txn_id: &base64::encode_config(
appservice::event::push_events::v1::Request { Self::calculate_hash(&pdu_ids),
events: &pdu_jsons, base64::URL_SAFE_NO_PAD,
txn_id: &base64::encode_config( ),
Self::calculate_hash(&pdu_ids), },
base64::URL_SAFE_NO_PAD, )
), .await
}, .map(|_response| kind.clone())
) .map_err(|e| (kind, e));
.await
.map(|_response| (server.clone(), is_appservice))
.map_err(|e| (server, is_appservice, e));
drop(permit); drop(permit);
response response
} else { }
let pdu_jsons = pdu_ids OutgoingKind::Push(user, pushkey) => {
.iter() let pdus = pdu_ids
.map(|pdu_id| { .iter()
Ok::<_, (Box<ServerName>, Error)>( .map(|pdu_id| {
// TODO: check room version and remove event_id if needed Ok::<_, (Vec<u8>, Error)>(
serde_json::from_str( db.rooms
PduEvent::convert_to_outgoing_federation_event( .get_pdu_from_id(pdu_id)
rooms .map_err(|e| (pushkey.clone(), e))?
.get_pdu_json_from_id(pdu_id) .ok_or_else(|| {
.map_err(|e| (server.clone(), e))? (
.ok_or_else(|| { pushkey.clone(),
( Error::bad_database(
server.clone(), "[Push] Event in servernamepduids not found in db.",
Error::bad_database( ),
"Event in servernamepduids not found in db.", )
), })?,
)
})?,
)
.json()
.get(),
) )
.expect("Raw<..> is always valid"), })
.filter_map(|r| r.ok())
.collect::<Vec<_>>();
for pdu in pdus {
// Redacted events are not notification targets (we don't send push for them)
if pdu.unsigned.get("redacted_because").is_some() {
continue;
}
let userid =
UserId::try_from(utils::string_from_bytes(user).map_err(|_| {
(
OutgoingKind::Push(user.clone(), pushkey.clone()),
Error::bad_database("Invalid push user string in db."),
)
})?)
.map_err(|_| {
(
OutgoingKind::Push(user.clone(), pushkey.clone()),
Error::bad_database("Invalid push user id in db."),
)
})?;
let mut senderkey = user.clone();
senderkey.push(0xff);
senderkey.extend_from_slice(pushkey);
let pusher = match db
.pusher
.get_pusher(&senderkey)
.map_err(|e| (OutgoingKind::Push(user.clone(), pushkey.clone()), e))?
{
Some(pusher) => pusher,
None => continue,
};
let rules_for_user = db
.account_data
.get::<push_rules::PushRulesEvent>(None, &userid, EventType::PushRules)
.unwrap_or_default()
.map(|ev| ev.content.global)
.unwrap_or_else(|| push::Ruleset::server_default(&userid));
let unread: UInt = db
.rooms
.notification_count(&userid, &pdu.room_id)
.map_err(|e| (kind.clone(), e))?
.try_into()
.expect("notifiation count can't go that high");
let permit = db.sending.maximum_requests.acquire().await;
let _response = pusher::send_push_notice(
&userid,
unread,
&pusher,
rules_for_user,
&pdu,
db,
) )
.await
.map(|_response| kind.clone())
.map_err(|e| (kind.clone(), e));
drop(permit);
}
Ok(OutgoingKind::Push(user.clone(), pushkey.clone()))
}
OutgoingKind::Normal(server) => {
let pdu_jsons = pdu_ids
.iter()
.map(|pdu_id| {
Ok::<_, (OutgoingKind, Error)>(
// TODO: check room version and remove event_id if needed
serde_json::from_str(
PduEvent::convert_to_outgoing_federation_event(
db.rooms
.get_pdu_json_from_id(pdu_id)
.map_err(|e| (OutgoingKind::Normal(server.clone()), e))?
.ok_or_else(|| {
(
OutgoingKind::Normal(server.clone()),
Error::bad_database(
"[Normal] Event in servernamepduids not found in db.",
),
)
})?,
)
.json()
.get(),
)
.expect("Raw<..> is always valid"),
)
})
.filter_map(|r| r.ok())
.collect::<Vec<_>>();
let permit = db.sending.maximum_requests.acquire().await;
let response = server_server::send_request(
&db.globals,
&*server,
send_transaction_message::v1::Request {
origin: db.globals.server_name(),
pdus: &pdu_jsons,
edus: &[],
origin_server_ts: SystemTime::now(),
transaction_id: &base64::encode_config(
Self::calculate_hash(&pdu_ids),
base64::URL_SAFE_NO_PAD,
),
},
)
.await
.map(|response| {
for pdu in response.pdus {
if pdu.1.is_err() {
warn!("Failed to send to {}: {:?}", server, pdu);
}
}
kind.clone()
}) })
.filter_map(|r| r.ok()) .map_err(|e| (kind, e));
.collect::<Vec<_>>();
let permit = maximum_requests.acquire().await;
let response = server_server::send_request(
&globals,
server.clone(),
send_transaction_message::v1::Request {
origin: globals.server_name(),
pdus: &pdu_jsons,
edus: &[],
origin_server_ts: SystemTime::now(),
transaction_id: &base64::encode_config(
Self::calculate_hash(&pdu_ids),
base64::URL_SAFE_NO_PAD,
),
},
)
.await
.map(|_response| (server.clone(), is_appservice))
.map_err(|e| (server, is_appservice, e));
drop(permit); drop(permit);
response response
}
} }
} }
fn parse_servercurrentpdus(key: IVec) -> Result<(IVec, Box<ServerName>, IVec, bool)> { fn parse_servercurrentpdus(key: &IVec) -> Result<(OutgoingKind, IVec)> {
let key2 = key.clone();
let mut parts = key2.splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element");
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| {
Error::bad_database("Invalid server bytes in server_currenttransaction")
})?;
// Appservices start with a plus // Appservices start with a plus
let (server, is_appservice) = if server.starts_with('+') { Ok::<_, Error>(if key.starts_with(b"+") {
(&server[1..], true) let mut parts = key[1..].splitn(2, |&b| b == 0xff);
let server = parts.next().expect("splitn always returns one element");
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let server = utils::string_from_bytes(&server).map_err(|_| {
Error::bad_database("Invalid server bytes in server_currenttransaction")
})?;
(
OutgoingKind::Appservice(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction")
})?),
IVec::from(pdu),
)
} else if key.starts_with(b"$") {
let mut parts = key[1..].splitn(3, |&b| b == 0xff);
let user = parts.next().expect("splitn always returns one element");
let pushkey = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
let pdu = parts
.next()
.ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
(
OutgoingKind::Push(user.to_vec(), pushkey.to_vec()),
IVec::from(pdu),
)
} else { } else {
(&*server, false) let mut parts = key.splitn(2, |&b| b == 0xff);
};
let server = parts.next().expect("splitn always returns one element");
Ok::<_, Error>(( let pdu = parts
key, .next()
Box::<ServerName>::try_from(server).map_err(|_| { .ok_or_else(|| Error::bad_database("Invalid bytes in servercurrentpdus."))?;
Error::bad_database("Invalid server string in server_currenttransaction") let server = utils::string_from_bytes(&server).map_err(|_| {
})?, Error::bad_database("Invalid server bytes in server_currenttransaction")
IVec::from(pdu), })?;
is_appservice,
)) (
OutgoingKind::Normal(Box::<ServerName>::try_from(server).map_err(|_| {
Error::bad_database("Invalid server string in server_currenttransaction")
})?),
IVec::from(pdu),
)
})
} }
#[tracing::instrument(skip(self, globals))] #[tracing::instrument(skip(self, globals))]
pub async fn send_federation_request<T: OutgoingRequest>( pub async fn send_federation_request<T: OutgoingRequest>(
&self, &self,
globals: &crate::database::globals::Globals, globals: &crate::database::globals::Globals,
destination: Box<ServerName>, destination: &ServerName,
request: T, request: T,
) -> Result<T::IncomingResponse> ) -> Result<T::IncomingResponse>
where where

@ -148,7 +148,7 @@ impl Uiaa {
device_id: &DeviceId, device_id: &DeviceId,
uiaainfo: Option<&UiaaInfo>, uiaainfo: Option<&UiaaInfo>,
) -> Result<()> { ) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -170,7 +170,7 @@ impl Uiaa {
device_id: &DeviceId, device_id: &DeviceId,
session: &str, session: &str,
) -> Result<UiaaInfo> { ) -> Result<UiaaInfo> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());

@ -9,6 +9,7 @@ use ruma::{
}, },
encryption::DeviceKeys, encryption::DeviceKeys,
events::{AnyToDeviceEvent, EventType}, events::{AnyToDeviceEvent, EventType},
identifiers::MxcUri,
serde::Raw, serde::Raw,
DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UInt, UserId, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UInt, UserId,
}; };
@ -150,21 +151,22 @@ impl Users {
} }
/// Get a the avatar_url of a user. /// Get a the avatar_url of a user.
pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<String>> { pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<MxcUri>> {
self.userid_avatarurl self.userid_avatarurl
.get(user_id.to_string())? .get(user_id.to_string())?
.map_or(Ok(None), |bytes| { .map(|bytes| {
Ok(Some(utils::string_from_bytes(&bytes).map_err(|_| { let s = utils::string_from_bytes(&bytes)
Error::bad_database("Avatar URL in db is invalid.") .map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
})?)) MxcUri::try_from(s).map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
}) })
.transpose()
} }
/// Sets a new avatar_url or removes it if avatar_url is None. /// Sets a new avatar_url or removes it if avatar_url is None.
pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<String>) -> Result<()> { pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<MxcUri>) -> Result<()> {
if let Some(avatar_url) = avatar_url { if let Some(avatar_url) = avatar_url {
self.userid_avatarurl self.userid_avatarurl
.insert(user_id.to_string(), &*avatar_url)?; .insert(user_id.to_string(), avatar_url.to_string().as_str())?;
} else { } else {
self.userid_avatarurl.remove(user_id.to_string())?; self.userid_avatarurl.remove(user_id.to_string())?;
} }
@ -183,7 +185,7 @@ impl Users {
// This method should never be called for nonexistent users. // This method should never be called for nonexistent users.
assert!(self.exists(user_id)?); assert!(self.exists(user_id)?);
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -206,7 +208,7 @@ impl Users {
/// Removes a device from a user. /// Removes a device from a user.
pub fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> { pub fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -232,7 +234,7 @@ impl Users {
/// Returns an iterator over all device ids of this user. /// Returns an iterator over all device ids of this user.
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<Box<DeviceId>>> { pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<Box<DeviceId>>> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
// All devices have metadata // All devices have metadata
self.userdeviceid_metadata self.userdeviceid_metadata
@ -252,7 +254,7 @@ impl Users {
/// Replaces the access token of one device. /// Replaces the access token of one device.
pub fn set_token(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> { pub fn set_token(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -280,7 +282,7 @@ impl Users {
one_time_key_value: &OneTimeKey, one_time_key_value: &OneTimeKey,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(device_id.as_bytes()); key.extend_from_slice(device_id.as_bytes());
@ -303,10 +305,8 @@ impl Users {
.expect("OneTimeKey::to_string always works"), .expect("OneTimeKey::to_string always works"),
)?; )?;
self.userid_lastonetimekeyupdate.insert( self.userid_lastonetimekeyupdate
&user_id.to_string().as_bytes(), .insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
&globals.next_count()?.to_be_bytes(),
)?;
Ok(()) Ok(())
} }
@ -314,7 +314,7 @@ impl Users {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn last_one_time_keys_update(&self, user_id: &UserId) -> Result<u64> { pub fn last_one_time_keys_update(&self, user_id: &UserId) -> Result<u64> {
self.userid_lastonetimekeyupdate self.userid_lastonetimekeyupdate
.get(&user_id.to_string().as_bytes())? .get(&user_id.as_bytes())?
.map(|bytes| { .map(|bytes| {
utils::u64_from_bytes(&bytes).map_err(|_| { utils::u64_from_bytes(&bytes).map_err(|_| {
Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.") Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.")
@ -330,18 +330,16 @@ impl Users {
key_algorithm: &DeviceKeyAlgorithm, key_algorithm: &DeviceKeyAlgorithm,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<Option<(DeviceKeyId, OneTimeKey)>> { ) -> Result<Option<(DeviceKeyId, OneTimeKey)>> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(device_id.as_bytes()); prefix.extend_from_slice(device_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
prefix.push(b'"'); // Annoying quotation mark prefix.push(b'"'); // Annoying quotation mark
prefix.extend_from_slice(key_algorithm.to_string().as_bytes()); prefix.extend_from_slice(key_algorithm.as_ref().as_bytes());
prefix.push(b':'); prefix.push(b':');
self.userid_lastonetimekeyupdate.insert( self.userid_lastonetimekeyupdate
&user_id.to_string().as_bytes(), .insert(&user_id.as_bytes(), &globals.next_count()?.to_be_bytes())?;
&globals.next_count()?.to_be_bytes(),
)?;
self.onetimekeyid_onetimekeys self.onetimekeyid_onetimekeys
.scan_prefix(&prefix) .scan_prefix(&prefix)
@ -371,7 +369,7 @@ impl Users {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
) -> Result<BTreeMap<DeviceKeyAlgorithm, UInt>> { ) -> Result<BTreeMap<DeviceKeyAlgorithm, UInt>> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -407,7 +405,7 @@ impl Users {
rooms: &super::rooms::Rooms, rooms: &super::rooms::Rooms,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -432,7 +430,7 @@ impl Users {
) -> Result<()> { ) -> Result<()> {
// TODO: Check signatures // TODO: Check signatures
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
// Master key // Master key
@ -530,9 +528,9 @@ impl Users {
rooms: &super::rooms::Rooms, rooms: &super::rooms::Rooms,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut key = target_id.to_string().as_bytes().to_vec(); let mut key = target_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(key_id.to_string().as_bytes()); key.extend_from_slice(key_id.as_bytes());
let mut cross_signing_key = let mut cross_signing_key =
serde_json::from_slice::<serde_json::Value>(&self.keyid_key.get(&key)?.ok_or( serde_json::from_slice::<serde_json::Value>(&self.keyid_key.get(&key)?.ok_or(
@ -615,14 +613,14 @@ impl Users {
continue; continue;
} }
let mut key = room_id.to_string().as_bytes().to_vec(); let mut key = room_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&count); key.extend_from_slice(&count);
self.keychangeid_userid.insert(key, &*user_id.to_string())?; self.keychangeid_userid.insert(key, &*user_id.to_string())?;
} }
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(&count); key.extend_from_slice(&count);
self.keychangeid_userid.insert(key, &*user_id.to_string())?; self.keychangeid_userid.insert(key, &*user_id.to_string())?;
@ -635,7 +633,7 @@ impl Users {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
) -> Result<Option<DeviceKeys>> { ) -> Result<Option<DeviceKeys>> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(device_id.as_bytes()); key.extend_from_slice(device_id.as_bytes());
@ -722,7 +720,7 @@ impl Users {
content: serde_json::Value, content: serde_json::Value,
globals: &super::globals::Globals, globals: &super::globals::Globals,
) -> Result<()> { ) -> Result<()> {
let mut key = target_user_id.to_string().as_bytes().to_vec(); let mut key = target_user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
key.extend_from_slice(target_device_id.as_bytes()); key.extend_from_slice(target_device_id.as_bytes());
key.push(0xff); key.push(0xff);
@ -749,7 +747,7 @@ impl Users {
) -> Result<Vec<Raw<AnyToDeviceEvent>>> { ) -> Result<Vec<Raw<AnyToDeviceEvent>>> {
let mut events = Vec::new(); let mut events = Vec::new();
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(device_id.as_bytes()); prefix.extend_from_slice(device_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
@ -771,7 +769,7 @@ impl Users {
device_id: &DeviceId, device_id: &DeviceId,
until: u64, until: u64,
) -> Result<()> { ) -> Result<()> {
let mut prefix = user_id.to_string().as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);
prefix.extend_from_slice(device_id.as_bytes()); prefix.extend_from_slice(device_id.as_bytes());
prefix.push(0xff); prefix.push(0xff);
@ -806,7 +804,7 @@ impl Users {
device_id: &DeviceId, device_id: &DeviceId,
device: &Device, device: &Device,
) -> Result<()> { ) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -829,7 +827,7 @@ impl Users {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
) -> Result<Option<Device>> { ) -> Result<Option<Device>> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec(); let mut userdeviceid = user_id.as_bytes().to_vec();
userdeviceid.push(0xff); userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes()); userdeviceid.extend_from_slice(device_id.as_bytes());
@ -843,7 +841,7 @@ impl Users {
} }
pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> { pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> {
let mut key = user_id.to_string().as_bytes().to_vec(); let mut key = user_id.as_bytes().to_vec();
key.push(0xff); key.push(0xff);
self.userdeviceid_metadata self.userdeviceid_metadata

@ -4,7 +4,6 @@ pub mod client_server;
mod database; mod database;
mod error; mod error;
mod pdu; mod pdu;
mod push_rules;
mod ruma_wrapper; mod ruma_wrapper;
pub mod server_server; pub mod server_server;
mod utils; mod utils;

@ -8,7 +8,6 @@ pub mod server_server;
mod database; mod database;
mod error; mod error;
mod pdu; mod pdu;
mod push_rules;
mod ruma_wrapper; mod ruma_wrapper;
mod utils; mod utils;
@ -20,11 +19,15 @@ pub use rocket::State;
use ruma::api::client::error::ErrorKind; use ruma::api::client::error::ErrorKind;
pub use ruma_wrapper::{ConduitResult, Ruma, RumaResponse}; pub use ruma_wrapper::{ConduitResult, Ruma, RumaResponse};
use rocket::figment::{ use rocket::{
providers::{Env, Format, Toml}, catch, catchers,
Figment, fairing::AdHoc,
figment::{
providers::{Env, Format, Toml},
Figment,
},
routes, Request,
}; };
use rocket::{catch, catchers, fairing::AdHoc, routes, Request};
use tracing::span; use tracing::span;
use tracing_subscriber::{prelude::*, Registry}; use tracing_subscriber::{prelude::*, Registry};
@ -74,7 +77,9 @@ fn setup_rocket() -> (rocket::Rocket, Config) {
client_server::get_filter_route, client_server::get_filter_route,
client_server::create_filter_route, client_server::create_filter_route,
client_server::set_global_account_data_route, client_server::set_global_account_data_route,
client_server::set_room_account_data_route,
client_server::get_global_account_data_route, client_server::get_global_account_data_route,
client_server::get_room_account_data_route,
client_server::set_displayname_route, client_server::set_displayname_route,
client_server::get_displayname_route, client_server::get_displayname_route,
client_server::set_avatar_url_route, client_server::set_avatar_url_route,
@ -152,6 +157,7 @@ fn setup_rocket() -> (rocket::Rocket, Config) {
client_server::get_key_changes_route, client_server::get_key_changes_route,
client_server::get_pushers_route, client_server::get_pushers_route,
client_server::set_pushers_route, client_server::set_pushers_route,
// client_server::third_party_route,
client_server::upgrade_room_route, client_server::upgrade_room_route,
server_server::get_server_version_route, server_server::get_server_version_route,
server_server::get_server_keys_route, server_server::get_server_keys_route,
@ -159,7 +165,10 @@ fn setup_rocket() -> (rocket::Rocket, Config) {
server_server::get_public_rooms_route, server_server::get_public_rooms_route,
server_server::get_public_rooms_filtered_route, server_server::get_public_rooms_filtered_route,
server_server::send_transaction_message_route, server_server::send_transaction_message_route,
server_server::get_event_route,
server_server::get_missing_events_route, server_server::get_missing_events_route,
server_server::get_room_state_ids_route,
server_server::create_invite_route,
server_server::get_profile_information_route, server_server::get_profile_information_route,
], ],
) )
@ -175,8 +184,7 @@ fn setup_rocket() -> (rocket::Rocket, Config) {
.await .await
.expect("config is valid"); .expect("config is valid");
data.sending data.sending.start_handler(&data);
.start_handler(&data.globals, &data.rooms, &data.appservice);
Ok(rocket.manage(data)) Ok(rocket.manage(data))
})); }));
@ -201,6 +209,9 @@ async fn main() {
rocket.launch().await.unwrap(); rocket.launch().await.unwrap();
} else { } else {
std::env::set_var("CONDUIT_LOG", config.log);
pretty_env_logger::init_custom_env("CONDUIT_LOG");
let root = span!(tracing::Level::INFO, "app_start", work_units = 2); let root = span!(tracing::Level::INFO, "app_start", work_units = 2);
let _enter = root.enter(); let _enter = root.enter();
@ -209,7 +220,7 @@ async fn main() {
} }
#[catch(404)] #[catch(404)]
fn not_found_catcher(_req: &'_ Request<'_>) -> String { fn not_found_catcher(_: &Request<'_>) -> String {
"404 Not Found".to_owned() "404 Not Found".to_owned()
} }

@ -1,4 +1,5 @@
use crate::Error; use crate::Error;
use log::error;
use ruma::{ use ruma::{
events::{ events::{
pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent, pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent,
@ -9,14 +10,9 @@ use ruma::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use std::{ use std::{cmp::Ordering, collections::BTreeMap, convert::TryFrom, time::UNIX_EPOCH};
collections::BTreeMap,
convert::{TryFrom, TryInto},
sync::Arc,
time::UNIX_EPOCH,
};
#[derive(Deserialize, Serialize, Debug)] #[derive(Clone, Deserialize, Serialize, Debug)]
pub struct PduEvent { pub struct PduEvent {
pub event_id: EventId, pub event_id: EventId,
pub room_id: RoomId, pub room_id: RoomId,
@ -32,8 +28,8 @@ pub struct PduEvent {
pub auth_events: Vec<EventId>, pub auth_events: Vec<EventId>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>, pub redacts: Option<EventId>,
#[serde(default, skip_serializing_if = "serde_json::Map::is_empty")] #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: serde_json::Map<String, serde_json::Value>, pub unsigned: BTreeMap<String, serde_json::Value>,
pub hashes: EventHash, pub hashes: EventHash,
pub signatures: BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>>, pub signatures: BTreeMap<Box<ServerName>, BTreeMap<ServerSigningKeyId, String>>,
} }
@ -170,22 +166,17 @@ impl PduEvent {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> { pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> {
let json = format!( let json = json!({
r#"{{"content":{},"type":"{}","event_id":"{}","sender":"{}","origin_server_ts":{},"unsigned":{},"state_key":"{}"}}"#, "content": self.content,
self.content, "type": self.kind,
self.kind, "event_id": self.event_id,
self.event_id, "sender": self.sender,
self.sender, "origin_server_ts": self.origin_server_ts,
self.origin_server_ts, "unsigned": self.unsigned,
serde_json::to_string(&self.unsigned).expect("Map::to_string always works"), "state_key": self.state_key,
self.state_key });
.as_ref()
.expect("state events have state keys")
);
Raw::from_json( serde_json::from_value(json).expect("Raw::from_value always works")
serde_json::value::RawValue::from_string(json).expect("our string is valid json"),
)
} }
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
@ -240,72 +231,98 @@ impl PduEvent {
) )
.expect("Raw::from_value always works") .expect("Raw::from_value always works")
} }
pub fn from_id_val(
event_id: &EventId,
mut json: CanonicalJsonObject,
) -> Result<Self, serde_json::Error> {
json.insert(
"event_id".to_string(),
to_canonical_value(event_id).expect("event_id is a valid Value"),
);
serde_json::from_value(serde_json::to_value(json).expect("valid JSON"))
}
} }
impl From<&state_res::StateEvent> for PduEvent { impl state_res::Event for PduEvent {
fn from(pdu: &state_res::StateEvent) -> Self { fn event_id(&self) -> &EventId {
Self { &self.event_id
event_id: pdu.event_id(), }
room_id: pdu.room_id().clone(),
sender: pdu.sender().clone(), fn room_id(&self) -> &RoomId {
origin_server_ts: (pdu &self.room_id
.origin_server_ts() }
.duration_since(UNIX_EPOCH)
.expect("time is valid") fn sender(&self) -> &UserId {
.as_millis() as u64) &self.sender
.try_into() }
.expect("time is valid"), fn kind(&self) -> EventType {
kind: pdu.kind(), self.kind.clone()
content: pdu.content().clone(), }
state_key: Some(pdu.state_key()),
prev_events: pdu.prev_event_ids(), fn content(&self) -> serde_json::Value {
depth: *pdu.depth(), self.content.clone()
auth_events: pdu.auth_events(), }
redacts: pdu.redacts().cloned(), fn origin_server_ts(&self) -> std::time::SystemTime {
unsigned: pdu.unsigned().clone().into_iter().collect(), UNIX_EPOCH + std::time::Duration::from_millis(self.origin_server_ts.into())
hashes: pdu.hashes().clone(), }
signatures: pdu.signatures(),
} fn state_key(&self) -> Option<String> {
self.state_key.clone()
}
fn prev_events(&self) -> Vec<EventId> {
self.prev_events.to_vec()
}
fn depth(&self) -> &UInt {
&self.depth
}
fn auth_events(&self) -> Vec<EventId> {
self.auth_events.to_vec()
}
fn redacts(&self) -> Option<&EventId> {
self.redacts.as_ref()
}
fn hashes(&self) -> &EventHash {
&self.hashes
}
fn signatures(&self) -> BTreeMap<Box<ServerName>, BTreeMap<ruma::ServerSigningKeyId, String>> {
self.signatures.clone()
}
fn unsigned(&self) -> &BTreeMap<String, serde_json::Value> {
&self.unsigned
} }
} }
impl PduEvent { // These impl's allow us to dedup state snapshots when resolving state
pub fn convert_for_state_res(&self) -> Arc<state_res::StateEvent> { // for incoming events (federation/send/{txn}).
Arc::new( impl Eq for PduEvent {}
// For consistency of eventId (just in case) we use the one impl PartialEq for PduEvent {
// generated by conduit for everything. fn eq(&self, other: &Self) -> bool {
state_res::StateEvent::from_id_value( self.event_id == other.event_id
self.event_id.clone(), }
json!({ }
"event_id": self.event_id, impl PartialOrd for PduEvent {
"room_id": self.room_id, fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
"sender": self.sender, self.event_id.partial_cmp(&other.event_id)
"origin_server_ts": self.origin_server_ts, }
"type": self.kind, }
"content": self.content, impl Ord for PduEvent {
"state_key": self.state_key, fn cmp(&self, other: &Self) -> Ordering {
"prev_events": self.prev_events, self.event_id.cmp(&other.event_id)
"depth": self.depth,
"auth_events": self.auth_events,
"redacts": self.redacts,
"unsigned": self.unsigned,
"hashes": self.hashes,
"signatures": self.signatures,
}),
)
.expect("all conduit PDUs are state events"),
)
} }
} }
/// Generates a correct eventId for the incoming pdu. /// Generates a correct eventId for the incoming pdu.
/// ///
/// Returns a tuple of the new `EventId` and the PDU with the eventId inserted as a `serde_json::Value`. /// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap<String, CanonicalJsonValue>`.
pub(crate) fn process_incoming_pdu( pub(crate) fn gen_event_id_canonical_json(
pdu: &Raw<ruma::events::pdu::Pdu>, pdu: &Raw<ruma::events::pdu::Pdu>,
) -> (EventId, CanonicalJsonObject) { ) -> crate::Result<(EventId, CanonicalJsonObject)> {
let mut value = let value = serde_json::from_str(pdu.json().get()).map_err(|e| {
serde_json::from_str(pdu.json().get()).expect("A Raw<...> is always valid JSON"); error!("{:?}: {:?}", pdu, e);
Error::BadServerResponse("Invalid PDU in server response")
})?;
let event_id = EventId::try_from(&*format!( let event_id = EventId::try_from(&*format!(
"${}", "${}",
@ -314,12 +331,7 @@ pub(crate) fn process_incoming_pdu(
)) ))
.expect("ruma's reference hashes are valid event ids"); .expect("ruma's reference hashes are valid event ids");
value.insert( Ok((event_id, value))
"event_id".to_owned(),
to_canonical_value(&event_id).expect("EventId is a valid CanonicalJsonValue"),
);
(event_id, value)
} }
/// Build the start of a PDU in order to add it to the `Database`. /// Build the start of a PDU in order to add it to the `Database`.
@ -328,7 +340,7 @@ pub struct PduBuilder {
#[serde(rename = "type")] #[serde(rename = "type")]
pub event_type: EventType, pub event_type: EventType,
pub content: serde_json::Value, pub content: serde_json::Value,
pub unsigned: Option<serde_json::Map<String, serde_json::Value>>, pub unsigned: Option<BTreeMap<String, serde_json::Value>>,
pub state_key: Option<String>, pub state_key: Option<String>,
pub redacts: Option<EventId>, pub redacts: Option<EventId>,
} }

@ -1,256 +0,0 @@
use ruma::{
push::{
Action, ConditionalPushRule, ConditionalPushRuleInit, ContentPushRule, OverridePushRule,
PatternedPushRule, PatternedPushRuleInit, PushCondition, RoomMemberCountIs, Ruleset, Tweak,
UnderridePushRule,
},
UserId,
};
pub fn default_pushrules(user_id: &UserId) -> Ruleset {
let mut rules = Ruleset::default();
rules.add(ContentPushRule(contains_user_name_rule(&user_id)));
for rule in vec![
master_rule(),
suppress_notices_rule(),
invite_for_me_rule(),
member_event_rule(),
contains_display_name_rule(),
tombstone_rule(),
roomnotif_rule(),
] {
rules.add(OverridePushRule(rule));
}
for rule in vec![
call_rule(),
encrypted_room_one_to_one_rule(),
room_one_to_one_rule(),
message_rule(),
encrypted_rule(),
] {
rules.add(UnderridePushRule(rule));
}
rules
}
pub fn master_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::DontNotify],
default: true,
enabled: false,
rule_id: ".m.rule.master".to_owned(),
conditions: vec![],
}
.into()
}
pub fn suppress_notices_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::DontNotify],
default: true,
enabled: true,
rule_id: ".m.rule.suppress_notices".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.msgtype".to_owned(),
pattern: "m.notice".to_owned(),
}],
}
.into()
}
pub fn invite_for_me_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.invite_for_me".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.membership".to_owned(),
pattern: "m.invite".to_owned(),
}],
}
.into()
}
pub fn member_event_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::DontNotify],
default: true,
enabled: true,
rule_id: ".m.rule.member_event".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "content.membership".to_owned(),
pattern: "type".to_owned(),
}],
}
.into()
}
pub fn contains_display_name_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(true)),
],
default: true,
enabled: true,
rule_id: ".m.rule.contains_display_name".to_owned(),
conditions: vec![PushCondition::ContainsDisplayName],
}
.into()
}
pub fn tombstone_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
default: true,
enabled: true,
rule_id: ".m.rule.tombstone".to_owned(),
conditions: vec![
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.tombstone".to_owned(),
},
PushCondition::EventMatch {
key: "state_key".to_owned(),
pattern: "".to_owned(),
},
],
}
.into()
}
pub fn roomnotif_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
default: true,
enabled: true,
rule_id: ".m.rule.roomnotif".to_owned(),
conditions: vec![
PushCondition::EventMatch {
key: "content.body".to_owned(),
pattern: "@room".to_owned(),
},
PushCondition::SenderNotificationPermission {
key: "room".to_owned(),
},
],
}
.into()
}
pub fn contains_user_name_rule(user_id: &UserId) -> PatternedPushRule {
PatternedPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(true)),
],
default: true,
enabled: true,
rule_id: ".m.rule.contains_user_name".to_owned(),
pattern: user_id.localpart().to_owned(),
}
.into()
}
pub fn call_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("ring".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.call".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.call.invite".to_owned(),
}],
}
.into()
}
pub fn encrypted_room_one_to_one_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.encrypted_room_one_to_one".to_owned(),
conditions: vec![
PushCondition::RoomMemberCount {
is: RoomMemberCountIs::from(2_u32.into()..),
},
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.encrypted".to_owned(),
},
],
}
.into()
}
pub fn room_one_to_one_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![
Action::Notify,
Action::SetTweak(Tweak::Sound("default".to_owned())),
Action::SetTweak(Tweak::Highlight(false)),
],
default: true,
enabled: true,
rule_id: ".m.rule.room_one_to_one".to_owned(),
conditions: vec![
PushCondition::RoomMemberCount {
is: RoomMemberCountIs::from(2_u32.into()..),
},
PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.message".to_owned(),
},
],
}
.into()
}
pub fn message_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
default: true,
enabled: true,
rule_id: ".m.rule.message".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.message".to_owned(),
}],
}
.into()
}
pub fn encrypted_rule() -> ConditionalPushRule {
ConditionalPushRuleInit {
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
default: true,
enabled: true,
rule_id: ".m.rule.encrypted".to_owned(),
conditions: vec![PushCondition::EventMatch {
key: "type".to_owned(),
pattern: "m.room.encrypted".to_owned(),
}],
}
.into()
}

@ -1,17 +1,14 @@
use crate::Error; use crate::Error;
use ruma::{ use ruma::{
api::OutgoingResponse,
identifiers::{DeviceId, UserId}, identifiers::{DeviceId, UserId},
Outgoing, Outgoing,
}; };
use std::{ use std::ops::Deref;
convert::{TryInto},
ops::Deref,
};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use { use {
crate::utils, crate::utils,
ruma::api::{AuthScheme, OutgoingRequest},
log::{debug, warn}, log::{debug, warn},
rocket::{ rocket::{
data::{ data::{
@ -24,8 +21,9 @@ use {
tokio::io::AsyncReadExt, tokio::io::AsyncReadExt,
Request, State, Request, State,
}, },
ruma::api::{AuthScheme, IncomingRequest},
std::convert::TryFrom,
std::io::Cursor, std::io::Cursor,
std::convert::TryFrom,
}; };
/// This struct converts rocket requests into ruma structs by converting them into http requests /// This struct converts rocket requests into ruma structs by converting them into http requests
@ -39,12 +37,9 @@ pub struct Ruma<T: Outgoing> {
} }
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
impl<'a, T: Outgoing + OutgoingRequest> FromTransformedData<'a> for Ruma<T> impl<'a, T: Outgoing> FromTransformedData<'a> for Ruma<T>
where where
<T as Outgoing>::Incoming: TryFrom<http::request::Request<std::vec::Vec<u8>>> + std::fmt::Debug, T::Incoming: IncomingRequest,
<<T as Outgoing>::Incoming as std::convert::TryFrom<
http::request::Request<std::vec::Vec<u8>>,
>>::Error: std::fmt::Debug,
{ {
type Error = (); type Error = ();
type Owned = Data; type Owned = Data;
@ -61,6 +56,8 @@ where
request: &'a Request<'_>, request: &'a Request<'_>,
outcome: Transformed<'a, Self>, outcome: Transformed<'a, Self>,
) -> FromDataFuture<'a, Self, Self::Error> { ) -> FromDataFuture<'a, Self, Self::Error> {
let metadata = T::Incoming::METADATA;
Box::pin(async move { Box::pin(async move {
let data = rocket::try_outcome!(outcome.owned()); let data = rocket::try_outcome!(outcome.owned());
let db = request let db = request
@ -85,7 +82,7 @@ where
.and_then(|as_token| as_token.as_str()) .and_then(|as_token| as_token.as_str())
.map_or(false, |as_token| token.as_deref() == Some(as_token)) .map_or(false, |as_token| token.as_deref() == Some(as_token))
}) { }) {
match T::METADATA.authentication { match metadata.authentication {
AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => { AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => {
let user_id = request.get_query_value::<String>("user_id").map_or_else( let user_id = request.get_query_value::<String>("user_id").map_or_else(
|| { || {
@ -117,7 +114,7 @@ where
AuthScheme::None => (None, None, true), AuthScheme::None => (None, None, true),
} }
} else { } else {
match T::METADATA.authentication { match metadata.authentication {
AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => { AuthScheme::AccessToken | AuthScheme::QueryOnlyAccessToken => {
if let Some(token) = token { if let Some(token) = token {
match db.users.find_from_token(&token).unwrap() { match db.users.find_from_token(&token).unwrap() {
@ -149,10 +146,9 @@ where
let mut body = Vec::new(); let mut body = Vec::new();
handle.read_to_end(&mut body).await.unwrap(); handle.read_to_end(&mut body).await.unwrap();
let http_request = http_request.body(body.clone()).unwrap(); let http_request = http_request.body(&*body).unwrap();
debug!("{:?}", http_request); debug!("{:?}", http_request);
match <T::Incoming as IncomingRequest>::try_from_http_request(http_request) {
match <T as Outgoing>::Incoming::try_from(http_request) {
Ok(t) => Success(Ruma { Ok(t) => Success(Ruma {
body: t, body: t,
sender_user, sender_user,
@ -183,9 +179,9 @@ impl<T: Outgoing> Deref for Ruma<T> {
/// This struct converts ruma responses into rocket http responses. /// This struct converts ruma responses into rocket http responses.
pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>; pub type ConduitResult<T> = std::result::Result<RumaResponse<T>, Error>;
pub struct RumaResponse<T: TryInto<http::Response<Vec<u8>>>>(pub T); pub struct RumaResponse<T: OutgoingResponse>(pub T);
impl<T: TryInto<http::Response<Vec<u8>>>> From<T> for RumaResponse<T> { impl<T: OutgoingResponse> From<T> for RumaResponse<T> {
fn from(t: T) -> Self { fn from(t: T) -> Self {
Self(t) Self(t)
} }
@ -194,12 +190,11 @@ impl<T: TryInto<http::Response<Vec<u8>>>> From<T> for RumaResponse<T> {
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T> impl<'r, 'o, T> Responder<'r, 'o> for RumaResponse<T>
where where
T: Send + TryInto<http::Response<Vec<u8>>>, T: Send + OutgoingResponse,
T::Error: Send,
'o: 'r, 'o: 'r,
{ {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
let http_response: Result<http::Response<_>, _> = self.0.try_into(); let http_response: Result<http::Response<_>, _> = self.0.try_into_http_response();
match http_response { match http_response {
Ok(http_response) => { Ok(http_response) => {
let mut response = rocket::response::Response::build(); let mut response = rocket::response::Response::build();
@ -225,6 +220,7 @@ where
"Access-Control-Allow-Headers", "Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization", "Origin, X-Requested-With, Content-Type, Accept, Authorization",
); );
response.raw_header("Access-Control-Max-Age", "86400");
response.ok() response.ok()
} }
Err(_) => Err(Status::InternalServerError), Err(_) => Err(Status::InternalServerError),

File diff suppressed because it is too large Load Diff

@ -2,7 +2,6 @@ use argon2::{Config, Variant};
use cmp::Ordering; use cmp::Ordering;
use rand::prelude::*; use rand::prelude::*;
use ruma::serde::{try_from_json_map, CanonicalJsonError, CanonicalJsonObject}; use ruma::serde::{try_from_json_map, CanonicalJsonError, CanonicalJsonObject};
use sled::IVec;
use std::{ use std::{
cmp, cmp,
convert::TryInto, convert::TryInto,
@ -71,9 +70,9 @@ pub fn calculate_hash(password: &str) -> Result<String, argon2::Error> {
} }
pub fn common_elements( pub fn common_elements(
mut iterators: impl Iterator<Item = impl Iterator<Item = IVec>>, mut iterators: impl Iterator<Item = impl Iterator<Item = Vec<u8>>>,
check_order: impl Fn(&IVec, &IVec) -> Ordering, check_order: impl Fn(&[u8], &[u8]) -> Ordering,
) -> Option<impl Iterator<Item = IVec>> { ) -> Option<impl Iterator<Item = Vec<u8>>> {
let first_iterator = iterators.next()?; let first_iterator = iterators.next()?;
let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>(); let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>();

@ -1,3 +1,4 @@
# For use in our CI only. This requires a build artifact created by a previous run pipline stage to be placed in cached_target/release/conduit
FROM valkum/docker-rust-ci:latest as builder FROM valkum/docker-rust-ci:latest as builder
WORKDIR /workdir WORKDIR /workdir
@ -19,24 +20,30 @@ WORKDIR /workdir
RUN curl -OL "https://github.com/caddyserver/caddy/releases/download/v2.2.1/caddy_2.2.1_linux_amd64.tar.gz" RUN curl -OL "https://github.com/caddyserver/caddy/releases/download/v2.2.1/caddy_2.2.1_linux_amd64.tar.gz"
RUN tar xzf caddy_2.2.1_linux_amd64.tar.gz RUN tar xzf caddy_2.2.1_linux_amd64.tar.gz
COPY --from=builder /workdir/target/debug/conduit /workdir/conduit COPY cached_target/release/conduit /workdir/conduit
RUN chmod +x /workdir/conduit
RUN chmod +x /workdir/caddy
COPY Rocket-example.toml Rocket.toml COPY conduit-example.toml conduit.toml
ENV SERVER_NAME=localhost ENV SERVER_NAME=localhost
ENV ROCKET_LOG=normal ENV ROCKET_LOG=normal
ENV CONDUIT_CONFIG=/workdir/conduit.toml
RUN sed -i "s/port = 14004/port = 8008/g" Rocket.toml RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml
RUN echo "federation_enabled = true" >> Rocket.toml RUN echo "allow_federation = true" >> conduit.toml
RUN echo "allow_encryption = true" >> conduit.toml
RUN echo "allow_registration = true" >> conduit.toml
RUN echo "log = \"info,rocket=info,_=off,sled=off\"" >> conduit.toml
RUN sed -i "s/address = \"127.0.0.1\"/address = \"0.0.0.0\"/g" conduit.toml
# Enabled Caddy auto cert generation for complement provided CA. # Enabled Caddy auto cert generation for complement provided CA.
RUN echo '{"apps":{"http":{"https_port":8448,"servers":{"srv0":{"listen":[":8448"],"routes":[{"match":[{"host":["your.server.name"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:8008"}]}]}]}],"terminal":true}],"tls_connection_policies": [{"match": {"sni": ["your.server.name"]}}]}}},"pki": {"certificate_authorities": {"local": {"name": "Complement CA","root": {"certificate": "/ca/ca.crt","private_key": "/ca/ca.key"},"intermediate": {"certificate": "/ca/ca.crt","private_key": "/ca/ca.key"}}}},"tls":{"automation":{"policies":[{"subjects":["your.server.name"],"issuer":{"module":"internal"},"on_demand":true},{"issuer":{"module":"internal", "ca": "local"}}]}}}}' > caddy.json RUN echo '{"logging":{"logs":{"default":{"level":"WARN"}}}, "apps":{"http":{"https_port":8448,"servers":{"srv0":{"listen":[":8448"],"routes":[{"match":[{"host":["your.server.name"]}],"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:8008"}]}]}]}],"terminal":true}],"tls_connection_policies": [{"match": {"sni": ["your.server.name"]}}]}}},"pki": {"certificate_authorities": {"local": {"name": "Complement CA","root": {"certificate": "/ca/ca.crt","private_key": "/ca/ca.key"},"intermediate": {"certificate": "/ca/ca.crt","private_key": "/ca/ca.key"}}}},"tls":{"automation":{"policies":[{"subjects":["your.server.name"],"issuer":{"module":"internal"},"on_demand":true},{"issuer":{"module":"internal", "ca": "local"}}]}}}}' > caddy.json
EXPOSE 8008 8448 EXPOSE 8008 8448
CMD ([ -z "${COMPLEMENT_CA}" ] && echo "Error: Need Complement PKI support" && true) || \ CMD ([ -z "${COMPLEMENT_CA}" ] && echo "Error: Need Complement PKI support" && true) || \
sed -i "s/server_name = \"your.server.name\"/server_name = \"${SERVER_NAME}\"/g" Rocket.toml && \ sed -i "s/#server_name = \"your.server.name\"/server_name = \"${SERVER_NAME}\"/g" conduit.toml && \
sed -i "s/your.server.name/${SERVER_NAME}/g" caddy.json && \ sed -i "s/your.server.name/${SERVER_NAME}/g" caddy.json && \
/workdir/caddy start --config caddy.json > /dev/null && \ /workdir/caddy start --config caddy.json > /dev/null && \
/workdir/conduit /workdir/conduit

@ -1,71 +1,335 @@
/event/ does not allow access to events before the user joined
/event/ on joined room works
/event/ on non world readable room does not work
/joined_members return joined members /joined_members return joined members
/joined_rooms returns only joined rooms /joined_rooms returns only joined rooms
/whois
3pid invite join valid signature but revoked keys are rejected 3pid invite join valid signature but revoked keys are rejected
3pid invite join valid signature but unreachable ID server are rejected 3pid invite join valid signature but unreachable ID server are rejected
3pid invite join with wrong but valid signature are rejected 3pid invite join with wrong but valid signature are rejected
A change to displayname should appear in incremental /sync
A full_state incremental update returns all state
A full_state incremental update returns only recent timeline
A message sent after an initial sync appears in the timeline of an incremental sync.
A next_batch token can be used in the v1 messages API
A pair of events which redact each other should be ignored
A pair of servers can establish a join in a v2 room
A prev_batch token can be used in the v1 messages API
AS can create a user
AS can create a user with an underscore
AS can create a user with inhibit_login
AS can set avatar for ghosted users
AS can set displayname for ghosted users
AS can't set displayname for random users
AS cannot create users outside its own namespace AS cannot create users outside its own namespace
AS user (not ghost) can join room without registering
AS user (not ghost) can join room without registering, with user_id query param
After changing password, a different session no longer works by default
After changing password, can log in with new password
After changing password, can't log in with old password
After changing password, different sessions can optionally be kept
After changing password, existing session still works
After deactivating account, can't log in with an email After deactivating account, can't log in with an email
After deactivating account, can't log in with password
Alias creators can delete alias with no ops Alias creators can delete alias with no ops
Alias creators can delete canonical alias with no ops Alias creators can delete canonical alias with no ops
Alternative server names do not cause a routing loop Alternative server names do not cause a routing loop
An event which redacts an event in a different room should be ignored
An event which redacts itself should be ignored
Asking for a remote rooms list, but supplying the local server's name, returns the local rooms list
Backfill checks the events requested belong to the room
Backfill works correctly with history visibility set to joined
Backfilled events whose prev_events are in a different room do not allow cross-room back-pagination
Banned servers cannot /event_auth
Banned servers cannot /invite
Banned servers cannot /make_join
Banned servers cannot /make_leave
Banned servers cannot /send_join
Banned servers cannot /send_leave
Banned servers cannot backfill
Banned servers cannot get missing events
Banned servers cannot get room state
Banned servers cannot get room state ids
Banned servers cannot send events
Banned user is kicked and may not rejoin until unbanned
Both GET and PUT work Both GET and PUT work
Can /sync newly created room
Can add account data Can add account data
Can add account data to room
Can add tag Can add tag
Can claim one time key using POST
Can claim remote one time key using POST
Can create filter Can create filter
Can deactivate account
Can delete canonical alias Can delete canonical alias
Can download file 'ascii'
Can download file 'name with spaces'
Can download file 'name;with;semicolons'
Can download filter
Can download specifying a different ASCII file name
Can download specifying a different Unicode file name
Can download with Unicode file name locally
Can download with Unicode file name over federation
Can download without a file name locally
Can download without a file name over federation
Can forget room you've been kicked from
Can get 'm.room.name' state for a departed room (SPEC-216)
Can get account data without syncing
Can get remote public room list
Can get room account data without syncing
Can get rooms/{roomId}/members
Can get rooms/{roomId}/members for a departed room (SPEC-216)
Can get rooms/{roomId}/state for a departed room (SPEC-216)
Can invite users to invite-only rooms Can invite users to invite-only rooms
Can list tags for a room Can list tags for a room
Can logout all devices Can logout all devices
Can logout current device Can logout current device
Can paginate public room list
Can pass a JSON filter as a query parameter
Can query device keys using POST
Can query remote device keys using POST
Can query specific device keys using POST
Can re-join room if re-invited Can re-join room if re-invited
Can read configuration endpoint Can read configuration endpoint
Can receive redactions from regular users over federation in room version 1
Can receive redactions from regular users over federation in room version 2
Can receive redactions from regular users over federation in room version 3
Can receive redactions from regular users over federation in room version 4
Can receive redactions from regular users over federation in room version 5
Can receive redactions from regular users over federation in room version 6
Can recv a device message using /sync Can recv a device message using /sync
Can recv a device message using /sync
Can recv device messages over federation
Can recv device messages until they are acknowledged Can recv device messages until they are acknowledged
Can recv device messages until they are acknowledged
Can reject invites over federation for rooms with version 1
Can reject invites over federation for rooms with version 2
Can reject invites over federation for rooms with version 3
Can reject invites over federation for rooms with version 4
Can reject invites over federation for rooms with version 5
Can reject invites over federation for rooms with version 6
Can remove tag Can remove tag
Can search public room list
Can send a message directly to a device using PUT /sendToDevice Can send a message directly to a device using PUT /sendToDevice
Can send a message directly to a device using PUT /sendToDevice
Can send a to-device message to two users which both receive it using /sync Can send a to-device message to two users which both receive it using /sync
Can send image in room message
Can send messages with a wildcard device id Can send messages with a wildcard device id
Can send messages with a wildcard device id
Can send messages with a wildcard device id to two devices Can send messages with a wildcard device id to two devices
Can send messages with a wildcard device id to two devices
Can sync Can sync
Can sync a joined room
Can sync a room with a message with a transaction id
Can sync a room with a single message
Can upload device keys
Can upload with ASCII file name Can upload with ASCII file name
Can upload with Unicode file name Can upload with Unicode file name
Can upload without a file name Can upload without a file name
Can't deactivate account with wrong password
Can't forget room you're still in
Changes to state are included in an gapped incremental sync
Changes to state are included in an incremental sync
Changing the actions of an unknown default rule fails with 404 Changing the actions of an unknown default rule fails with 404
Changing the actions of an unknown rule fails with 404 Changing the actions of an unknown rule fails with 404
Checking local federation server Checking local federation server
Creators can delete alias
Current state appears in timeline in private history Current state appears in timeline in private history
Current state appears in timeline in private history with many messages before Current state appears in timeline in private history with many messages before
DELETE /device/{deviceId}
DELETE /device/{deviceId} requires UI auth user to match device owner
DELETE /device/{deviceId} with no body gives a 401
Deleted tags appear in an incremental v2 /sync Deleted tags appear in an incremental v2 /sync
Deleting a non-existent alias should return a 404 Deleting a non-existent alias should return a 404
Device list doesn't change if remote server is down
Device messages over federation wake up /sync
Device messages wake up /sync Device messages wake up /sync
Device messages wake up /sync
Device messages with the same txn_id are deduplicated Device messages with the same txn_id are deduplicated
Device messages with the same txn_id are deduplicated
Enabling an unknown default rule fails with 404
Event size limits
Event with an invalid signature in the send_join response should not cause room join to fail
Events come down the correct room Events come down the correct room
Events whose auth_events are in the wrong room do not mess up the room state
Existing members see new members' join events
Federation key API allows unsigned requests for keys
Federation key API can act as a notary server via a GET request
Federation key API can act as a notary server via a POST request
Federation rejects inbound events where the prev_events cannot be found
Fetching eventstream a second time doesn't yield the message again
Forgetting room does not show up in v2 /sync
Full state sync includes joined rooms
GET /capabilities is present and well formed for registered user
GET /device/{deviceId} GET /device/{deviceId}
GET /device/{deviceId} gives a 404 for unknown devices GET /device/{deviceId} gives a 404 for unknown devices
GET /devices GET /devices
GET /directory/room/:room_alias yields room ID
GET /events initially
GET /events with negative 'limit' GET /events with negative 'limit'
GET /events with non-numeric 'limit' GET /events with non-numeric 'limit'
GET /events with non-numeric 'timeout' GET /events with non-numeric 'timeout'
GET /initialSync initially
GET /joined_rooms lists newly-created room GET /joined_rooms lists newly-created room
GET /login yields a set of flows GET /login yields a set of flows
GET /media/r0/download can fetch the value again GET /media/r0/download can fetch the value again
GET /profile/:user_id/avatar_url publicly accessible GET /profile/:user_id/avatar_url publicly accessible
GET /profile/:user_id/displayname publicly accessible GET /profile/:user_id/displayname publicly accessible
GET /publicRooms includes avatar URLs
GET /publicRooms lists newly-created room GET /publicRooms lists newly-created room
GET /publicRooms lists rooms
GET /r0/capabilities is not public
GET /register yields a set of flows GET /register yields a set of flows
GET /rooms/:room_id/joined_members fetches my membership
GET /rooms/:room_id/messages returns a message
GET /rooms/:room_id/state fetches entire room state GET /rooms/:room_id/state fetches entire room state
GET /rooms/:room_id/state/m.room.member/:user_id fetches my membership GET /rooms/:room_id/state/m.room.member/:user_id fetches my membership
GET /rooms/:room_id/state/m.room.member/:user_id?format=event fetches my membership event
GET /rooms/:room_id/state/m.room.name gets name
GET /rooms/:room_id/state/m.room.power_levels can fetch levels
GET /rooms/:room_id/state/m.room.power_levels fetches powerlevels
GET /rooms/:room_id/state/m.room.topic gets topic
Get left notifs for other users in sync and /keys/changes when user leaves
Getting messages going forward is limited for a departed room (SPEC-216)
Getting push rules doesn't corrupt the cache SYN-390 Getting push rules doesn't corrupt the cache SYN-390
Getting state IDs checks the events requested belong to the room
Getting state checks the events requested belong to the room
Ghost user must register before joining room
Guest non-joined user cannot call /events on default room
Guest non-joined user cannot call /events on invited room
Guest non-joined user cannot call /events on joined room
Guest non-joined user cannot call /events on shared room
Guest non-joined users can get individual state for world_readable rooms
Guest non-joined users can get individual state for world_readable rooms after leaving
Guest non-joined users can get state for world_readable rooms
Guest non-joined users cannot room initalSync for non-world_readable rooms
Guest non-joined users cannot send messages to guest_access rooms if not joined
Guest user can set display names
Guest user cannot call /events globally
Guest user cannot upgrade other users
Guest users can accept invites to private rooms over federation
Guest users can join guest_access rooms
Guest users can send messages to guest_access rooms if joined
If a device list update goes missing, the server resyncs on the next one
If remote user leaves room we no longer receive device updates
If remote user leaves room, changes device and rejoins we see update in /keys/changes
If remote user leaves room, changes device and rejoins we see update in sync
Inbound /make_join rejects attempts to join rooms where all users have left
Inbound /v1/make_join rejects remote attempts to join local users to rooms
Inbound /v1/send_join rejects incorrectly-signed joins
Inbound /v1/send_join rejects joins from other servers
Inbound /v1/send_leave rejects leaves from other servers
Inbound federation accepts a second soft-failed event
Inbound federation accepts attempts to join v2 rooms from servers with support
Inbound federation can backfill events
Inbound federation can get public room list
Inbound federation can get state for a room
Inbound federation can get state_ids for a room
Inbound federation can query profile data
Inbound federation can query room alias directory
Inbound federation can receive events
Inbound federation can receive invites via v1 API
Inbound federation can receive invites via v2 API
Inbound federation can receive redacted events
Inbound federation can receive v1 /send_join
Inbound federation can receive v2 /send_join
Inbound federation can return events
Inbound federation can return missing events for invite visibility
Inbound federation can return missing events for world_readable visibility
Inbound federation correctly soft fails events
Inbound federation of state requires event_id as a mandatory paramater
Inbound federation of state_ids requires event_id as a mandatory paramater
Inbound federation rejects attempts to join v1 rooms from servers without v1 support
Inbound federation rejects attempts to join v2 rooms from servers lacking version support
Inbound federation rejects attempts to join v2 rooms from servers only supporting v1
Inbound federation rejects invite rejections which include invalid JSON for room version 6
Inbound federation rejects invites which include invalid JSON for room version 6
Inbound federation rejects receipts from wrong remote
Inbound federation rejects remote attempts to join local users to rooms
Inbound federation rejects remote attempts to kick local users to rooms
Inbound federation rejects typing notifications from wrong remote
Inbound: send_join rejects invalid JSON for room version 6
Invalid JSON floats
Invalid JSON integers
Invalid JSON special values
Invited user can reject invite
Invited user can reject invite over federation
Invited user can reject invite over federation for empty room
Invited user can reject invite over federation several times
Invited user can see room metadata
Inviting an AS-hosted user asks the AS server
Lazy loading parameters in the filter are strictly boolean
Left rooms appear in the leave section of full state sync
Local delete device changes appear in v2 /sync
Local device key changes appear in /keys/changes
Local device key changes appear in v2 /sync
Local device key changes get to remote servers
Local new device changes appear in v2 /sync
Local non-members don't see posted message events
Local room members can get room messages
Local room members see posted message events
Local update device changes appear in v2 /sync
Local users can peek by room alias
Local users can peek into world_readable rooms by room ID
Message history can be paginated
Message history can be paginated over federation
Name/topic keys are correct
New account data appears in incremental v2 /sync
New read receipts appear in incremental v2 /sync
New room members see their own join event
New users appear in /keys/changes
Newly banned rooms appear in the leave section of incremental sync
Newly joined room is included in an incremental sync
Newly joined room is included in an incremental sync after invite
Newly left rooms appear in the leave section of gapped sync
Newly left rooms appear in the leave section of incremental sync
Newly updated tags appear in an incremental v2 /sync Newly updated tags appear in an incremental v2 /sync
Non-numeric ports in server names are rejected
Outbound federation can backfill events
Outbound federation can query profile data
Outbound federation can query room alias directory
Outbound federation can query v1 /send_join
Outbound federation can query v2 /send_join
Outbound federation can request missing events
Outbound federation can send events
Outbound federation can send invites via v1 API
Outbound federation can send invites via v2 API
Outbound federation can send room-join requests
Outbound federation correctly handles unsupported room versions
Outbound federation passes make_join failures through to the client
Outbound federation rejects backfill containing invalid JSON for events in room version 6
Outbound federation rejects m.room.create events with an unknown room version
Outbound federation rejects send_join responses with no m.room.create event
Outbound federation sends receipts
Outbound federation will ignore a missing event with bad JSON for room version 6
POST /createRoom creates a room with the given version
POST /createRoom ignores attempts to set the room version via creation_content
POST /createRoom makes a private room POST /createRoom makes a private room
POST /createRoom makes a private room with invites POST /createRoom makes a private room with invites
POST /createRoom makes a public room
POST /createRoom makes a room with a name
POST /createRoom makes a room with a topic
POST /createRoom rejects attempts to create rooms with numeric versions
POST /createRoom rejects attempts to create rooms with unknown versions
POST /createRoom with creation content
POST /join/:room_alias can join a room
POST /join/:room_alias can join a room with custom content
POST /join/:room_id can join a room POST /join/:room_id can join a room
POST /join/:room_id can join a room with custom content
POST /login as non-existing user is rejected POST /login as non-existing user is rejected
POST /login can log in as a user POST /login can log in as a user
POST /login can log in as a user with just the local part of the id POST /login can log in as a user with just the local part of the id
POST /login returns the same device_id as that in the request POST /login returns the same device_id as that in the request
POST /login wrong password is rejected POST /login wrong password is rejected
POST /media/r0/upload can create an upload POST /media/r0/upload can create an upload
POST /redact disallows redaction of event in different room
POST /register allows registration of usernames with '-'
POST /register allows registration of usernames with '.'
POST /register allows registration of usernames with '/'
POST /register allows registration of usernames with '3'
POST /register allows registration of usernames with '='
POST /register allows registration of usernames with '_'
POST /register allows registration of usernames with 'q'
POST /register can create a user POST /register can create a user
POST /register downcases capitals in usernames POST /register downcases capitals in usernames
POST /register rejects registration of usernames with '!' POST /register rejects registration of usernames with '!'
@ -88,41 +352,161 @@ POST /rooms/:room_id/ban can ban a user
POST /rooms/:room_id/invite can send an invite POST /rooms/:room_id/invite can send an invite
POST /rooms/:room_id/join can join a room POST /rooms/:room_id/join can join a room
POST /rooms/:room_id/leave can leave a room POST /rooms/:room_id/leave can leave a room
POST /rooms/:room_id/read_markers can create read marker
POST /rooms/:room_id/receipt can create receipts
POST /rooms/:room_id/redact/:event_id as original message sender redacts message
POST /rooms/:room_id/redact/:event_id as power user redacts message
POST /rooms/:room_id/redact/:event_id as random user does not redact message
POST /rooms/:room_id/send/:event_type sends a message
POST /rooms/:room_id/state/m.room.name sets name POST /rooms/:room_id/state/m.room.name sets name
POST /rooms/:room_id/state/m.room.topic sets topic POST /rooms/:room_id/state/m.room.topic sets topic
POST /rooms/:room_id/upgrade can upgrade a room version POST /rooms/:room_id/upgrade can upgrade a room version
POST rejects invalid utf-8 in JSON
POSTed media can be thumbnailed POSTed media can be thumbnailed
PUT /device/{deviceId} gives a 404 for unknown devices PUT /device/{deviceId} gives a 404 for unknown devices
PUT /device/{deviceId} updates device fields PUT /device/{deviceId} updates device fields
PUT /directory/room/:room_alias creates alias PUT /directory/room/:room_alias creates alias
PUT /profile/:user_id/avatar_url sets my avatar PUT /profile/:user_id/avatar_url sets my avatar
PUT /profile/:user_id/displayname sets my name PUT /profile/:user_id/displayname sets my name
PUT /rooms/:room_id/send/:event_type/:txn_id deduplicates the same txn id
PUT /rooms/:room_id/send/:event_type/:txn_id sends a message
PUT /rooms/:room_id/state/m.room.power_levels can set levels PUT /rooms/:room_id/state/m.room.power_levels can set levels
PUT /rooms/:room_id/typing/:user_id sets typing notification
PUT power_levels should not explode if the old power levels were empty PUT power_levels should not explode if the old power levels were empty
Peeked rooms only turn up in the sync for the device who peeked them
Previously left rooms don't appear in the leave section of sync
Push rules come down in an initial /sync Push rules come down in an initial /sync
Read markers appear in incremental v2 /sync Read markers appear in incremental v2 /sync
Read markers appear in initial v2 /sync Read markers appear in initial v2 /sync
Read markers can be updated Read markers can be updated
Read receipts appear in initial v2 /sync
Real non-joined user cannot call /events on default room
Real non-joined user cannot call /events on invited room
Real non-joined user cannot call /events on joined room
Real non-joined user cannot call /events on shared room
Real non-joined users can get individual state for world_readable rooms
Real non-joined users can get individual state for world_readable rooms after leaving
Real non-joined users can get state for world_readable rooms
Real non-joined users cannot room initalSync for non-world_readable rooms
Real non-joined users cannot send messages to guest_access rooms if not joined
Receipts must be m.read
Redaction of a redaction redacts the redaction reason
Regular users can add and delete aliases in the default room configuration Regular users can add and delete aliases in the default room configuration
Regular users can add and delete aliases when m.room.aliases is restricted
Regular users cannot create room aliases within the AS namespace
Regular users cannot register within the AS namespace
Remote media can be thumbnailed
Remote room alias queries can handle Unicode
Remote room members also see posted message events
Remote room members can get room messages
Remote user can backfill in a room with version 1
Remote user can backfill in a room with version 2
Remote user can backfill in a room with version 3
Remote user can backfill in a room with version 4
Remote user can backfill in a room with version 5
Remote user can backfill in a room with version 6
Remote users can join room by alias
Remote users may not join unfederated rooms
Request to logout with invalid an access token is rejected
Request to logout without an access token is rejected
Room aliases can contain Unicode
Room creation reports m.room.create to myself Room creation reports m.room.create to myself
Room creation reports m.room.member to myself Room creation reports m.room.member to myself
Room members can join a room with an overridden displayname
Room members can override their displayname on a room-specific basis
Room state at a rejected message event is the same as its predecessor
Room state at a rejected state event is the same as its predecessor
Rooms a user is invited to appear in an incremental sync Rooms a user is invited to appear in an incremental sync
Rooms a user is invited to appear in an initial sync Rooms a user is invited to appear in an initial sync
Rooms can be created with an initial invite list (SYN-205)
Server correctly handles incoming m.device_list_update
Server correctly handles transactions that break edu limits
Server correctly resyncs when client query keys and there is no remote cache
Server correctly resyncs when server leaves and rejoins a room
Server rejects invalid JSON in a version 6 room
Setting room topic reports m.room.topic to myself Setting room topic reports m.room.topic to myself
Should not be able to take over the room by pretending there is no PL event
Should reject keys claiming to belong to a different user Should reject keys claiming to belong to a different user
State from remote users is included in the state in the initial sync
State from remote users is included in the timeline in an incremental sync
State is included in the timeline in the initial sync
Sync can be polled for updates
Sync is woken up for leaves
Syncing a new room with a large timeline limit isn't limited
Tags appear in an initial v2 /sync Tags appear in an initial v2 /sync
Trying to get push rules with unknown rule_id fails with 404 Trying to get push rules with unknown rule_id fails with 404
Typing can be explicitly stopped
Typing events appear in gapped sync Typing events appear in gapped sync
Typing events appear in incremental sync Typing events appear in incremental sync
Typing events appear in initial sync Typing events appear in initial sync
Typing notification sent to local room members
Typing notifications also sent to remote room members
Typing notifications don't leak
Uninvited users cannot join the room Uninvited users cannot join the room
Unprivileged users can set m.room.topic if it only needs level 0
User appears in user directory User appears in user directory
User can create and send/receive messages in a room with version 1
User can create and send/receive messages in a room with version 2
User can create and send/receive messages in a room with version 3
User can create and send/receive messages in a room with version 4
User can create and send/receive messages in a room with version 5
User can create and send/receive messages in a room with version 6
User can invite local user to room with version 1
User can invite local user to room with version 2
User can invite local user to room with version 3
User can invite local user to room with version 4
User can invite local user to room with version 5
User can invite local user to room with version 6
User can invite remote user to room with version 1
User can invite remote user to room with version 2
User can invite remote user to room with version 3
User can invite remote user to room with version 4
User can invite remote user to room with version 5
User can invite remote user to room with version 6
User directory correctly update on display name change User directory correctly update on display name change
User in dir while user still shares private rooms User in dir while user still shares private rooms
User in shared private room does appear in user directory User in shared private room does appear in user directory
User is offline if they set_presence=offline in their sync User is offline if they set_presence=offline in their sync
User signups are forbidden from starting with '_'
Users can't delete other's aliases
Users cannot invite a user that is already in the room
Users cannot invite themselves to a room
Users cannot kick users from a room they are not in
Users cannot kick users who have already left a room
Users cannot set ban powerlevel higher than their own
Users cannot set kick powerlevel higher than their own
Users cannot set notifications powerlevel higher than their own
Users cannot set redact powerlevel higher than their own
Users receive device_list updates for their own devices
Users with sufficient power-level can delete other's aliases Users with sufficient power-level can delete other's aliases
Version responds 200 OK with valid structure Version responds 200 OK with valid structure
We can't peek into rooms with invited history_visibility
We can't peek into rooms with joined history_visibility
We can't peek into rooms with shared history_visibility
We don't send redundant membership state across incremental syncs by default
We should see our own leave event when rejecting an invite, even if history_visibility is restricted (riot-web/3462) We should see our own leave event when rejecting an invite, even if history_visibility is restricted (riot-web/3462)
We should see our own leave event, even if history_visibility is restricted (SYN-662)
Wildcard device messages over federation wake up /sync
Wildcard device messages wake up /sync Wildcard device messages wake up /sync
Wildcard device messages wake up /sync
avatar_url updates affect room member events
displayname updates affect room member events
local user can join room with version 1
local user can join room with version 2
local user can join room with version 3
local user can join room with version 4
local user can join room with version 5
local user can join room with version 6
m.room.history_visibility == "joined" allows/forbids appropriately for Guest users
m.room.history_visibility == "joined" allows/forbids appropriately for Real users
m.room.history_visibility == "world_readable" allows/forbids appropriately for Guest users
m.room.history_visibility == "world_readable" allows/forbids appropriately for Real users
query for user with no keys returns empty key dict query for user with no keys returns empty key dict
remote user can join room with version 1
remote user can join room with version 2
remote user can join room with version 3
remote user can join room with version 4
remote user can join room with version 5
remote user can join room with version 6
setting 'm.room.name' respects room powerlevel
setting 'm.room.power_levels' respects room powerlevel

Loading…
Cancel
Save