Keep track of State at event for state resolution

feat: first steps towards joining rooms over federation
Add state-res as a dependency of conduit
Add reverse_topological_power_sort before append_pdu
Implement statehashstatid_pduid tree for keeping track of state
Clean up implementation of state_hash as key for tracking state
next
Devin Ragotzy 2020-08-06 08:29:59 -04:00
parent 8e55623bde
commit c4f5a0a631
24 changed files with 818 additions and 356 deletions

206
Cargo.lock generated
View File

@ -75,6 +75,15 @@ dependencies = [
"opaque-debug 0.2.3",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "arc-swap"
version = "0.4.7"
@ -248,6 +257,17 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
dependencies = [
"num-integer",
"num-traits",
"time 0.1.43",
]
[[package]]
name = "cloudabi"
version = "0.1.0"
@ -281,6 +301,7 @@ dependencies = [
"serde",
"serde_json",
"sled",
"state-res",
"thiserror",
"tokio",
]
@ -456,6 +477,12 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "either"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
[[package]]
name = "encoding_rs"
version = "0.8.23"
@ -872,6 +899,15 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.6"
@ -951,6 +987,21 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matchers"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.8"
@ -1439,6 +1490,31 @@ dependencies = [
"syn",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
dependencies = [
"byteorder",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@ -1560,21 +1636,23 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.0.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"ruma-api",
"ruma-appservice-api",
"ruma-client-api",
"ruma-common",
"ruma-events",
"ruma-federation-api",
"ruma-identifiers",
"ruma-serde",
"ruma-signatures",
]
[[package]]
name = "ruma-api"
version = "0.17.0-alpha.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"http",
"percent-encoding",
@ -1589,7 +1667,7 @@ dependencies = [
[[package]]
name = "ruma-api-macros"
version = "0.17.0-alpha.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -1597,14 +1675,28 @@ dependencies = [
"syn",
]
[[package]]
name = "ruma-appservice-api"
version = "0.2.0-alpha.1"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"ruma-api",
"ruma-common",
"ruma-events",
"ruma-identifiers",
"serde",
"serde_json",
]
[[package]]
name = "ruma-client-api"
version = "0.10.0-alpha.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"assign",
"http",
"js_int",
"percent-encoding",
"ruma-api",
"ruma-common",
"ruma-events",
@ -1618,7 +1710,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.2.0"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"js_int",
"ruma-identifiers",
@ -1631,7 +1723,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.22.0-alpha.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"js_int",
"ruma-common",
@ -1646,7 +1738,7 @@ dependencies = [
[[package]]
name = "ruma-events-macros"
version = "0.22.0-alpha.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -1657,7 +1749,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.0.3"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"js_int",
"ruma-api",
@ -1672,7 +1764,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers"
version = "0.17.4"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"rand",
"ruma-identifiers-macros",
@ -1684,7 +1776,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-macros"
version = "0.17.4"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"proc-macro2",
"quote",
@ -1695,7 +1787,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.1.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"ruma-serde",
"serde",
@ -1706,7 +1798,7 @@ dependencies = [
[[package]]
name = "ruma-serde"
version = "0.2.3"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"form_urlencoded",
"itoa",
@ -1718,7 +1810,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.6.0-dev.1"
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
source = "git+https://github.com/ruma/ruma?rev=aff914050eb297bd82b8aafb12158c88a9e480e1#aff914050eb297bd82b8aafb12158c88a9e480e1"
dependencies = [
"base64 0.12.3",
"ring",
@ -1910,6 +2002,15 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sharded-slab"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.2.1"
@ -1983,6 +2084,22 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
[[package]]
name = "state-res"
version = "0.1.0"
source = "git+https://github.com/ruma/state-res#789c8140890e076d38b23fa1147c4ff0500c0d38"
dependencies = [
"itertools",
"js_int",
"maplit",
"ruma",
"serde",
"serde_json",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "stdweb"
version = "0.4.20"
@ -2104,6 +2221,15 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]]
name = "time"
version = "0.1.43"
@ -2251,9 +2377,21 @@ checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c"
dependencies = [
"cfg-if",
"log",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe233f4227389ab7df5b32649239da7ebe0b281824b4e84b342d04d3fd8c25e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.14"
@ -2263,6 +2401,48 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-log"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-serde"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79"
dependencies = [
"serde",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd165311cc4d7a555ad11cc77a37756df836182db0d81aac908c8184c584f40"
dependencies = [
"ansi_term",
"chrono",
"lazy_static",
"matchers",
"regex",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
"tracing-serde",
]
[[package]]
name = "try-lock"
version = "0.2.3"

View File

@ -16,9 +16,7 @@ edition = "2018"
#rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"] } # Used to handle requests
rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", features = ["tls"] }
#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
ruma = { git = "https://github.com/timokoesters/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "timo-fixes" } # Used for matrix spec type definitions and helpers
#ruma = { path = "../ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" } # Used for matrix spec type definitions and helpers
tokio = "0.2.22" # Used for long polling
sled = "0.32.0" # Used for storing data permanently
log = "0.4.8" # Used for emitting log entries
@ -33,6 +31,9 @@ reqwest = "0.10.6" # Used to send requests
thiserror = "1.0.19" # Used for conduit::Error type
image = { version = "0.23.4", default-features = false, features = ["jpeg", "png", "gif"] } # Used to generate thumbnails for images
base64 = "0.12.3" # Used to encode server public key
# state-res = { path = "../../state-res" }
state-res = { git = "https://github.com/ruma/state-res", version = "0.1.0" }
[features]
default = ["conduit_bin"]

View File

@ -75,7 +75,7 @@ pub fn get_register_available_route(
)]
pub fn register_route(
db: State<'_, Database>,
body: Ruma<register::Request>,
body: Ruma<register::IncomingRequest>,
) -> ConduitResult<register::Response> {
if db.globals.registration_disabled() {
return Err(Error::BadRequest(
@ -223,7 +223,7 @@ pub fn register_route(
)]
pub fn change_password_route(
db: State<'_, Database>,
body: Ruma<change_password::Request>,
body: Ruma<change_password::IncomingRequest>,
) -> ConduitResult<change_password::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
let device_id = body.device_id.as_ref().expect("user is authenticated");
@ -305,7 +305,7 @@ pub fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::Respon
)]
pub fn deactivate_route(
db: State<'_, Database>,
body: Ruma<deactivate::Request>,
body: Ruma<deactivate::IncomingRequest>,
) -> ConduitResult<deactivate::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
let device_id = body.device_id.as_ref().expect("user is authenticated");

View File

@ -26,7 +26,7 @@ pub fn create_alias_route(
db.rooms
.set_alias(&body.room_alias, Some(&body.room_id), &db.globals)?;
Ok(create_alias::Response.into())
Ok(create_alias::Response::new().into())
}
#[cfg_attr(
@ -39,7 +39,7 @@ pub fn delete_alias_route(
) -> ConduitResult<delete_alias::Response> {
db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
Ok(delete_alias::Response.into())
Ok(delete_alias::Response::new().into())
}
#[cfg_attr(
@ -60,11 +60,7 @@ pub async fn get_alias_route(
)
.await?;
return Ok(get_alias::Response {
room_id: response.room_id,
servers: response.servers,
}
.into());
return Ok(get_alias::Response::new(response.room_id, response.servers).into());
}
let room_id = db
@ -75,9 +71,5 @@ pub async fn get_alias_route(
"Room with alias not found.",
))?;
Ok(get_alias::Response {
room_id,
servers: vec![db.globals.server_name().to_string()],
}
.into())
Ok(get_alias::Response::new(room_id, vec![db.globals.server_name().to_string()]).into())
}

View File

@ -12,7 +12,7 @@ use rocket::get;
)]
pub fn get_context_route(
db: State<'_, Database>,
body: Ruma<get_context::Request>,
body: Ruma<get_context::IncomingRequest>,
) -> ConduitResult<get_context::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -75,18 +75,18 @@ pub fn get_context_route(
.map(|(_, pdu)| pdu.to_room_event())
.collect::<Vec<_>>();
Ok(get_context::Response {
start: start_token,
end: end_token,
events_before,
event: Some(base_event),
events_after,
state: db // TODO: State at event
.rooms
.room_state_full(&body.room_id)?
.values()
.map(|pdu| pdu.to_state_event())
.collect(),
}
.into())
let mut resp = get_context::Response::new();
resp.start = start_token;
resp.end = end_token;
resp.events_before = events_before;
resp.event = Some(base_event);
resp.events_after = events_after;
resp.state = db // TODO: State at event
.rooms
.room_state_full(&body.room_id)?
.values()
.map(|pdu| pdu.to_state_event())
.collect();
Ok(resp.into())
}

View File

@ -37,7 +37,7 @@ pub fn get_devices_route(
)]
pub fn get_device_route(
db: State<'_, Database>,
body: Ruma<get_device::Request>,
body: Ruma<get_device::IncomingRequest>,
_device_id: String,
) -> ConduitResult<get_device::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -56,7 +56,7 @@ pub fn get_device_route(
)]
pub fn update_device_route(
db: State<'_, Database>,
body: Ruma<update_device::Request>,
body: Ruma<update_device::IncomingRequest>,
_device_id: String,
) -> ConduitResult<update_device::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -80,7 +80,7 @@ pub fn update_device_route(
)]
pub fn delete_device_route(
db: State<'_, Database>,
body: Ruma<delete_device::Request>,
body: Ruma<delete_device::IncomingRequest>,
_device_id: String,
) -> ConduitResult<delete_device::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -127,7 +127,7 @@ pub fn delete_device_route(
)]
pub fn delete_devices_route(
db: State<'_, Database>,
body: Ruma<delete_devices::Request>,
body: Ruma<delete_devices::IncomingRequest>,
) -> ConduitResult<delete_devices::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
let device_id = body.device_id.as_ref().expect("user is authenticated");

View File

@ -6,7 +6,7 @@ use ruma::{
error::ErrorKind,
r0::{
directory::{
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
set_room_visibility,
},
room,
@ -14,6 +14,7 @@ use ruma::{
},
federation,
},
directory::PublicRoomsChunk,
events::{
room::{avatar, canonical_alias, guest_access, history_visibility, name, topic},
EventType,
@ -35,15 +36,15 @@ pub async fn get_public_rooms_filtered_route(
if let Some(other_server) = body
.server
.clone()
.filter(|server| server != &db.globals.server_name().as_str())
.filter(|server| server != db.globals.server_name().as_str())
{
let response = server_server::send_request(
&db,
other_server,
federation::directory::get_public_rooms::v1::Request {
limit: body.limit,
since: body.since.clone(),
room_network: federation::directory::get_public_rooms::v1::RoomNetwork::Matrix,
since: body.since.as_deref(),
room_network: ruma::directory::RoomNetwork::Matrix,
},
)
.await?;
@ -107,7 +108,7 @@ pub async fn get_public_rooms_filtered_route(
// TODO: Do not load full state?
let state = db.rooms.room_state_full(&room_id)?;
let chunk = directory::PublicRoomsChunk {
let chunk = PublicRoomsChunk {
aliases: Vec::new(),
canonical_alias: state
.get(&(EventType::RoomCanonicalAlias, "".to_owned()))
@ -272,7 +273,7 @@ pub async fn get_public_rooms_route(
body: get_public_rooms_filtered::IncomingRequest {
filter: None,
limit,
room_network: get_public_rooms_filtered::RoomNetwork::Matrix,
room_network: ruma::directory::RoomNetwork::Matrix,
server,
since,
},

View File

@ -7,23 +7,18 @@ use rocket::{get, post};
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/filter/<_>"))]
pub fn get_filter_route() -> ConduitResult<get_filter::Response> {
// TODO
Ok(get_filter::Response {
filter: filter::FilterDefinition {
event_fields: None,
event_format: None,
account_data: None,
room: None,
presence: None,
},
}
Ok(get_filter::Response::new(filter::FilterDefinition {
event_fields: None,
event_format: None,
account_data: None,
room: None,
presence: None,
})
.into())
}
#[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/user/<_>/filter"))]
pub fn create_filter_route() -> ConduitResult<create_filter::Response> {
// TODO
Ok(create_filter::Response {
filter_id: utils::random_string(10),
}
.into())
Ok(create_filter::Response::new(utils::random_string(10)).into())
}

View File

@ -167,7 +167,7 @@ pub fn claim_keys_route(
)]
pub fn upload_signing_keys_route(
db: State<'_, Database>,
body: Ruma<upload_signing_keys::Request>,
body: Ruma<upload_signing_keys::IncomingRequest>,
) -> ConduitResult<upload_signing_keys::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
let device_id = body.device_id.as_ref().expect("user is authenticated");

View File

@ -53,7 +53,7 @@ pub fn create_content_route(
)]
pub fn get_content_route(
db: State<'_, Database>,
body: Ruma<get_content::Request>,
body: Ruma<get_content::IncomingRequest>,
_server_name: String,
_media_id: String,
) -> ConduitResult<get_content::Response> {
@ -85,7 +85,7 @@ pub fn get_content_route(
)]
pub fn get_content_thumbnail_route(
db: State<'_, Database>,
body: Ruma<get_content_thumbnail::Request>,
body: Ruma<get_content_thumbnail::IncomingRequest>,
_server_name: String,
_media_id: String,
) -> ConduitResult<get_content_thumbnail::Response> {

View File

@ -20,6 +20,8 @@ use ruma::{
events::{room::member, EventType},
EventId, Raw, RoomId, RoomVersionId,
};
use state_res::StateEvent;
use std::{collections::BTreeMap, convert::TryFrom};
#[cfg(feature = "conduit_bin")]
@ -92,17 +94,73 @@ pub async fn join_room_by_id_route(
let send_join_response = server_server::send_request(
&db,
body.room_id.server_name().to_string(),
federation::membership::create_join_event::v2::Request {
federation::membership::create_join_event::v1::Request {
room_id: body.room_id.clone(),
event_id,
pdu_stub: serde_json::from_value::<Raw<_>>(join_event_stub_value)
pdu_stub: serde_json::from_value(join_event_stub_value)
.expect("Raw::from_value always works"),
},
)
.await?;
dbg!(send_join_response);
todo!("Take send_join_response and 'create' the room using that data");
dbg!(&send_join_response);
// todo!("Take send_join_response and 'create' the room using that data");
let mut event_map = send_join_response
.room_state
.state
.iter()
.map(|pdu| pdu.deserialize().map(StateEvent::Full))
.map(|ev| {
let ev = ev?;
Ok::<_, serde_json::Error>((ev.event_id(), ev))
})
.collect::<Result<BTreeMap<EventId, StateEvent>, _>>()
.map_err(|_| Error::bad_database("Invalid PDU found in db."))?;
let _auth_chain = send_join_response
.room_state
.auth_chain
.iter()
.flat_map(|pdu| pdu.deserialize().ok())
.map(StateEvent::Full)
.collect::<Vec<_>>();
// TODO make StateResolution's methods free functions ? or no self param ?
let sorted_events_ids = state_res::StateResolution::default()
.reverse_topological_power_sort(
&body.room_id,
&event_map.keys().cloned().collect::<Vec<_>>(),
&mut event_map,
&db.rooms,
&[], // TODO auth_diff: is this none since we have a set of resolved events we only want to sort
);
for ev_id in &sorted_events_ids {
// this is a `state_res::StateEvent` that holds a `ruma::Pdu`
let pdu = event_map.get(ev_id).ok_or_else(|| {
Error::Conflict("Found event_id in sorted events that is not in resolved state")
})?;
db.rooms.append_pdu(
PduBuilder {
room_id: pdu.room_id().unwrap_or(&body.room_id).clone(),
sender: pdu.sender().clone(),
event_type: pdu.kind(),
content: pdu.content().clone(),
unsigned: Some(
pdu.unsigned()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
),
state_key: pdu.state_key(),
redacts: pdu.redacts().cloned(),
},
&db.globals,
&db.account_data,
)?;
}
}
let event = member::MemberEventContent {
@ -127,10 +185,7 @@ pub async fn join_room_by_id_route(
&db.account_data,
)?;
Ok(join_room_by_id::Response {
room_id: body.room_id.clone(),
}
.into())
Ok(join_room_by_id::Response::new(body.room_id.clone()).into())
}
#[cfg_attr(
@ -140,7 +195,7 @@ pub async fn join_room_by_id_route(
pub async fn join_room_by_id_or_alias_route(
db: State<'_, Database>,
db2: State<'_, Database>,
body: Ruma<join_room_by_id_or_alias::Request>,
body: Ruma<join_room_by_id_or_alias::IncomingRequest>,
) -> ConduitResult<join_room_by_id_or_alias::Response> {
let room_id = match RoomId::try_from(body.room_id_or_alias.clone()) {
Ok(room_id) => room_id,
@ -148,7 +203,13 @@ pub async fn join_room_by_id_or_alias_route(
client_server::get_alias_route(
db,
Ruma {
body: alias::get_alias::IncomingRequest { room_alias },
body: alias::get_alias::IncomingRequest::try_from(http::Request::new(
serde_json::json!({ "room_alias": room_alias })
.to_string()
.as_bytes()
.to_vec(),
))
.unwrap(),
sender_id: body.sender_id.clone(),
device_id: body.device_id.clone(),
json_body: None,
@ -160,14 +221,32 @@ pub async fn join_room_by_id_or_alias_route(
}
};
// TODO ruma needs to implement the same constructors for the Incoming variants
let tps = if let Some(in_tps) = &body.third_party_signed {
Some(ruma::api::client::r0::membership::ThirdPartySigned {
token: &in_tps.token,
sender: &in_tps.sender,
signatures: in_tps.signatures.clone(),
mxid: &in_tps.mxid,
})
} else {
None
};
let body = Ruma {
sender_id: body.sender_id.clone(),
device_id: body.device_id.clone(),
json_body: None,
body: join_room_by_id::IncomingRequest {
room_id,
third_party_signed: body.third_party_signed.clone(),
},
body: join_room_by_id::IncomingRequest::try_from(http::Request::new(
serde_json::json!({
"room_id": room_id,
"third_party_signed": tps,
})
.to_string()
.as_bytes()
.to_vec(),
))
.unwrap(),
};
Ok(join_room_by_id_or_alias::Response {
@ -219,7 +298,7 @@ pub fn leave_room_route(
&db.account_data,
)?;
Ok(leave_room::Response.into())
Ok(leave_room::Response::new().into())
}
#[cfg_attr(
@ -266,7 +345,7 @@ pub fn invite_user_route(
)]
pub fn kick_user_route(
db: State<'_, Database>,
body: Ruma<kick_user::Request>,
body: Ruma<kick_user::IncomingRequest>,
) -> ConduitResult<kick_user::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -304,7 +383,7 @@ pub fn kick_user_route(
&db.account_data,
)?;
Ok(kick_user::Response.into())
Ok(kick_user::Response::new().into())
}
#[cfg_attr(
@ -313,7 +392,7 @@ pub fn kick_user_route(
)]
pub fn ban_user_route(
db: State<'_, Database>,
body: Ruma<ban_user::Request>,
body: Ruma<ban_user::IncomingRequest>,
) -> ConduitResult<ban_user::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -359,7 +438,7 @@ pub fn ban_user_route(
&db.account_data,
)?;
Ok(ban_user::Response.into())
Ok(ban_user::Response::new().into())
}
#[cfg_attr(
@ -368,7 +447,7 @@ pub fn ban_user_route(
)]
pub fn unban_user_route(
db: State<'_, Database>,
body: Ruma<unban_user::Request>,
body: Ruma<unban_user::IncomingRequest>,
) -> ConduitResult<unban_user::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -405,7 +484,7 @@ pub fn unban_user_route(
&db.account_data,
)?;
Ok(unban_user::Response.into())
Ok(unban_user::Response::new().into())
}
#[cfg_attr(
@ -414,13 +493,13 @@ pub fn unban_user_route(
)]
pub fn forget_room_route(
db: State<'_, Database>,
body: Ruma<forget_room::Request>,
body: Ruma<forget_room::IncomingRequest>,
) -> ConduitResult<forget_room::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
db.rooms.forget(&body.room_id, &sender_id)?;
Ok(forget_room::Response.into())
Ok(forget_room::Response::new().into())
}
#[cfg_attr(

View File

@ -1,8 +1,11 @@
use super::State;
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
use ruma::api::client::{
error::ErrorKind,
r0::message::{get_message_events, send_message_event},
use ruma::{
api::client::{
error::ErrorKind,
r0::message::{get_message_events, send_message_event},
},
events::EventContent,
};
use std::convert::TryInto;
@ -26,7 +29,7 @@ pub fn send_message_event_route(
PduBuilder {
room_id: body.room_id.clone(),
sender: sender_id.clone(),
event_type: body.event_type.clone(),
event_type: body.content.event_type().into(),
content: serde_json::from_str(
body.json_body
.ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?
@ -41,7 +44,7 @@ pub fn send_message_event_route(
&db.account_data,
)?;
Ok(send_message_event::Response { event_id }.into())
Ok(send_message_event::Response::new(event_id).into())
}
#[cfg_attr(
@ -50,7 +53,7 @@ pub fn send_message_event_route(
)]
pub fn get_message_events_route(
db: State<'_, Database>,
body: Ruma<get_message_events::Request>,
body: Ruma<get_message_events::IncomingRequest>,
) -> ConduitResult<get_message_events::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@ -92,13 +95,13 @@ pub fn get_message_events_route(
.map(|(_, pdu)| pdu.to_room_event())
.collect::<Vec<_>>();
Ok(get_message_events::Response {
start: Some(body.from.clone()),
end: end_token,
chunk: events_after,
state: Vec::new(),
}
.into())
let mut resp = get_message_events::Response::new();
resp.start = Some(body.from.clone());
resp.end = end_token;
resp.chunk = events_after;
resp.state = Vec::new();
Ok(resp.into())
}
get_message_events::Direction::Backward => {
let events_before = db
@ -116,13 +119,13 @@ pub fn get_message_events_route(
.map(|(_, pdu)| pdu.to_room_event())
.collect::<Vec<_>>();
Ok(get_message_events::Response {
start: Some(body.from.clone()),
end: start_token,
chunk: events_before,
state: Vec::new(),
}
.into())
let mut resp = get_message_events::Response::new();
resp.start = Some(body.from.clone());
resp.end = start_token;
resp.chunk = events_before;
resp.state = Vec::new();
Ok(resp.into())
}
}
}

View File

@ -315,7 +315,7 @@ pub fn create_room_route(
db.rooms.set_public(&room_id, true)?;
}
Ok(create_room::Response { room_id }.into())
Ok(create_room::Response::new(room_id).into())
}
#[cfg_attr(

View File

@ -1,5 +1,4 @@
use super::State;
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{utils, ConduitResult, Database, Error, Ruma};
use ruma::{
api::client::{
@ -18,10 +17,7 @@ use rocket::{get, post};
/// when logging in.
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))]
pub fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
Ok(get_login_types::Response {
flows: vec![get_login_types::LoginType::Password],
}
.into())
Ok(get_login_types::Response::new(vec![get_login_types::LoginType::Password]).into())
}
/// # `POST /_matrix/client/r0/login`
@ -40,15 +36,15 @@ pub fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
)]
pub fn login_route(
db: State<'_, Database>,
body: Ruma<login::Request>,
body: Ruma<login::IncomingRequest>,
) -> ConduitResult<login::Response> {
// Validate login method
let user_id =
// TODO: Other login methods
if let (login::UserInfo::MatrixId(username), login::LoginInfo::Password { password }) =
(body.user.clone(), body.login_info.clone())
if let (login::IncomingUserInfo::MatrixId(username), login::IncomingLoginInfo::Password { password }) =
(&body.user, &body.login_info)
{
let user_id = UserId::parse_with_server_name(username, db.globals.server_name())
let user_id = UserId::parse_with_server_name(username.to_string(), db.globals.server_name())
.map_err(|_| Error::BadRequest(
ErrorKind::InvalidUsername,
"Username is invalid."
@ -126,7 +122,7 @@ pub fn logout_route(
db.users.remove_device(&sender_id, device_id)?;
Ok(logout::Response.into())
Ok(logout::Response::new().into())
}
/// # `POST /_matrix/client/r0/logout/all`
@ -154,5 +150,5 @@ pub fn logout_all_route(
}
}
Ok(logout_all::Response.into())
Ok(logout_all::Response::new().into())
}

View File

@ -8,9 +8,9 @@ use ruma::{
send_state_event_for_empty_key, send_state_event_for_key,
},
},
events::{room::canonical_alias, EventType},
Raw,
events::{AnyStateEventContent, EventContent},
};
use std::convert::TryFrom;
#[cfg(feature = "conduit_bin")]
use rocket::{get, put};
@ -33,17 +33,10 @@ pub fn send_state_event_for_key_route(
)
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?;
if body.event_type == EventType::RoomCanonicalAlias {
let canonical_alias = serde_json::from_value::<
Raw<canonical_alias::CanonicalAliasEventContent>,
>(content.clone())
.expect("from_value::<Raw<..>> can never fail")
.deserialize()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid canonical alias."))?;
if let AnyStateEventContent::RoomCanonicalAlias(canonical_alias) = &body.content {
let mut aliases = canonical_alias.alt_aliases.clone();
let mut aliases = canonical_alias.alt_aliases;
if let Some(alias) = canonical_alias.alias {
if let Some(alias) = canonical_alias.alias.clone() {
aliases.push(alias);
}
@ -68,7 +61,7 @@ pub fn send_state_event_for_key_route(
PduBuilder {
room_id: body.room_id.clone(),
sender: sender_id.clone(),
event_type: body.event_type.clone(),
event_type: body.content.event_type().into(),
content,
unsigned: None,
state_key: Some(body.state_key.clone()),
@ -78,7 +71,7 @@ pub fn send_state_event_for_key_route(
&db.account_data,
)?;
Ok(send_state_event_for_key::Response { event_id }.into())
Ok(send_state_event_for_key::Response::new(event_id).into())
}
#[cfg_attr(
@ -93,25 +86,28 @@ pub fn send_state_event_for_empty_key_route(
let Ruma {
body:
send_state_event_for_empty_key::IncomingRequest {
room_id,
event_type,
data,
room_id, content, ..
},
sender_id,
device_id,
json_body,
} = body;
Ok(send_state_event_for_empty_key::Response {
event_id: send_state_event_for_key_route(
Ok(send_state_event_for_empty_key::Response::new(
send_state_event_for_key_route(
db,
Ruma {
body: send_state_event_for_key::IncomingRequest {
room_id,
event_type,
data,
state_key: "".to_owned(),
},
body: send_state_event_for_key::IncomingRequest::try_from(http::Request::new(
serde_json::json!({
"room_id": room_id,
"state_key": "",
"content": content,
})
.to_string()
.as_bytes()
.to_vec(),
))
.unwrap(),
sender_id,
device_id,
json_body,
@ -119,7 +115,7 @@ pub fn send_state_event_for_empty_key_route(
)?
.0
.event_id,
}
)
.into())
}

View File

@ -31,7 +31,7 @@ use std::{
)]
pub async fn sync_events_route(
db: State<'_, Database>,
body: Ruma<sync_events::Request>,
body: Ruma<sync_events::IncomingRequest>,
) -> ConduitResult<sync_events::Response> {
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
let device_id = body.device_id.as_ref().expect("user is authenticated");

View File

@ -1,6 +1,5 @@
use crate::ConduitResult;
use ruma::api::client::unversioned::get_supported_versions;
use std::collections::BTreeMap;
#[cfg(feature = "conduit_bin")]
use rocket::get;
@ -17,13 +16,11 @@ use rocket::get;
/// unstable features in their stable releases
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/versions"))]
pub fn get_supported_versions_route() -> ConduitResult<get_supported_versions::Response> {
let mut unstable_features = BTreeMap::new();
let mut resp =
get_supported_versions::Response::new(vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()]);
unstable_features.insert("org.matrix.e2e_cross_signing".to_owned(), true);
resp.unstable_features
.insert("org.matrix.e2e_cross_signing".to_owned(), true);
Ok(get_supported_versions::Response {
versions: vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()],
unstable_features,
}
.into())
Ok(resp.into())
}

View File

@ -97,8 +97,8 @@ impl Database {
},
pduid_pdu: db.open_tree("pduid_pdu")?,
eventid_pduid: db.open_tree("eventid_pduid")?,
roomstateid_pduid: db.open_tree("roomstateid_pduid")?,
roomid_pduleaves: db.open_tree("roomid_pduleaves")?,
roomstateid_pdu: db.open_tree("roomstateid_pdu")?,
alias_roomid: db.open_tree("alias_roomid")?,
aliasid_alias: db.open_tree("alias_roomid")?,
@ -111,6 +111,9 @@ impl Database {
userroomid_invited: db.open_tree("userroomid_invited")?,
roomuserid_invited: db.open_tree("roomuserid_invited")?,
userroomid_left: db.open_tree("userroomid_left")?,
stateid_pduid: db.open_tree("stateid_pduid")?,
pduid_statehash: db.open_tree("pduid_statehash")?,
},
account_data: account_data::AccountData {
roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,

View File

@ -9,7 +9,7 @@ use ruma::{
events::{
ignored_user_list,
room::{
join_rules, member,
member,
power_levels::{self, PowerLevelsEventContent},
redaction,
},
@ -18,19 +18,31 @@ use ruma::{
EventId, Raw, RoomAliasId, RoomId, UserId,
};
use sled::IVec;
use state_res::{event_auth, Requester, StateEvent, StateMap, StateStore};
use std::{
collections::{BTreeMap, HashMap},
collections::{hash_map::DefaultHasher, BTreeMap, HashMap},
convert::{TryFrom, TryInto},
hash::{Hash, Hasher},
mem,
result::Result as StdResult,
};
/// The unique identifier of each state group.
///
/// This is created when a state group is added to the database by
/// hashing the entire state.
pub type StateHashId = String;
/// This identifier consists of roomId + count. It represents a
/// unique event, it will never be overwritten or removed.
pub type PduId = IVec;
pub struct Rooms {
pub edus: edus::RoomEdus,
pub(super) pduid_pdu: sled::Tree, // PduId = RoomId + Count
pub(super) eventid_pduid: sled::Tree,
pub(super) roomid_pduleaves: sled::Tree,
pub(super) roomstateid_pdu: sled::Tree, // RoomStateId = Room + StateType + StateKey
pub(super) alias_roomid: sled::Tree,
pub(super) aliasid_alias: sled::Tree, // AliasId = RoomId + Count
pub(super) publicroomids: sled::Tree,
@ -42,9 +54,263 @@ pub struct Rooms {
pub(super) userroomid_invited: sled::Tree,
pub(super) roomuserid_invited: sled::Tree,
pub(super) userroomid_left: sled::Tree,
// STATE TREES
/// This holds the full current state, including the latest event.
pub(super) roomstateid_pduid: sled::Tree, // RoomStateId = Room + StateType + StateKey
/// This holds the full room state minus the latest event.
pub(super) pduid_statehash: sled::Tree, // PDU id -> StateHash
/// Also holds the full room state minus the latest event.
pub(super) stateid_pduid: sled::Tree, // StateId = StateHash + (EventType, StateKey)
}
impl StateStore for Rooms {
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> StdResult<StateEvent, String> {
let pid = self
.eventid_pduid
.get(event_id.as_bytes())
.map_err(|e| e.to_string())?
.ok_or_else(|| "PDU via room_id and event_id not found in the db.".to_owned())?;
utils::deserialize(
&self
.pduid_pdu
.get(pid)
.map_err(|e| e.to_string())?
.ok_or_else(|| "PDU via pduid not found in db.".to_owned())?,
)
.and_then(|pdu: StateEvent| {
// conduit's PDU's always contain a room_id but some
// of ruma's do not so this must be an Option
if pdu.room_id() == Some(room_id) {
Ok(pdu)
} else {
Err(Error::bad_database("Found PDU for incorrect room in db."))
}
})
.map_err(|e| e.to_string())
}
}
// These are the methods related to STATE resolution.
impl Rooms {
/// Generates a new StateHash and associates it with the incoming event.
///
/// This adds all current state events (not including the incoming event)
/// to `stateid_pduid` and adds the incoming event to `pduid_statehash`.
/// The incoming event is the `pdu_id` passed to this method.
pub fn append_state_pdu(&self, room_id: &RoomId, pdu_id: &[u8]) -> Result<StateHashId> {
let state_hash = self.new_state_hash_id(room_id)?;
let state = self.current_state_pduids(room_id)?;
let mut key = state_hash.as_bytes().to_vec();
key.push(0xff);
// TODO eventually we could avoid writing to the DB so much on every event
// by keeping track of the delta and write that every so often
for ((ev_ty, state_key), pid) in state {
let mut state_id = key.to_vec();
state_id.extend_from_slice(ev_ty.to_string().as_bytes());
key.push(0xff);
state_id.extend_from_slice(state_key.expect("state event").as_bytes());
key.push(0xff);
self.stateid_pduid.insert(&state_id, &pid)?;
}
// This event's state does not include the event itself. `current_state_pduids`
// uses `roomstateid_pduid` before the current event is inserted to the tree so the state
// will be everything up to but not including the incoming event.
self.pduid_statehash.insert(pdu_id, state_hash.as_bytes())?;
Ok(state_hash)
}
/// Builds a `StateMap` by iterating over all keys that start
/// with `state_hash`, this gives the full state at event "x".
pub fn get_statemap_by_hash(&self, state_hash: StateHashId) -> Result<StateMap<EventId>> {
self.stateid_pduid
.scan_prefix(state_hash.as_bytes())
.values()
.map(|pduid| {
self.pduid_pdu.get(&pduid?)?.map_or_else(
|| Err(Error::bad_database("Failed to find StateMap.")),
|b| {
serde_json::from_slice::<PduEvent>(&b)
.map_err(|_| Error::bad_database("Invalid PDU in db."))
},
)
})
.map(|pdu| {
let pdu = pdu?;
Ok(((pdu.kind, pdu.state_key), pdu.event_id))
})
.collect::<Result<StateMap<_>>>()
}
// TODO make this return Result
/// Fetches the previous StateHash ID to `current`.
pub fn prev_state_hash(&self, current: StateHashId) -> Option<StateHashId> {
let mut found = false;
for pair in self.pduid_statehash.iter().rev() {
let prev = utils::string_from_bytes(&pair.ok()?.1).ok()?;
if current == prev {
found = true;
}
if current != prev && found {
return Some(prev);
}
}
None
}
/// Fetch the current State using the `roomstateid_pduid` tree.
pub fn current_state_pduids(&self, room_id: &RoomId) -> Result<StateMap<PduId>> {
// TODO this could also scan roomstateid_pduid if we passed in room_id ?
self.roomstateid_pduid
.scan_prefix(room_id.as_bytes())
.values()
.map(|pduid| {
let pduid = &pduid?;
self.pduid_pdu.get(pduid)?.map_or_else(
|| {
Err(Error::bad_database(
"Failed to find current state of pduid's.",
))
},
|b| {
Ok((
serde_json::from_slice::<PduEvent>(&b)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
pduid.clone(),
))
},
)
})
.map(|pair| {
let (pdu, id) = pair?;
Ok(((pdu.kind, pdu.state_key), id))
})
.collect::<Result<StateMap<_>>>()
}
/// Returns the last state hash key added to the db.
pub fn current_state_hash(&self, room_id: &RoomId) -> Result<StateHashId> {
let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff);
// We must check here because this method is called outside and before
// `append_state_pdu` so the DB can be empty
if self.pduid_statehash.scan_prefix(prefix).next().is_none() {
// TODO use ring crate to hash
return Ok(room_id.as_str().to_owned());
}
self.pduid_statehash
.iter()
.next_back()
.map(|pair| {
utils::string_from_bytes(&pair?.1)
.map_err(|_| Error::bad_database("Invalid state hash string in db."))
})
.ok_or_else(|| Error::bad_database("No PDU's found for this room."))?
}
/// This fetches auth event_ids from the current state using the
/// full `roomstateid_pdu` tree.
pub fn get_auth_event_ids(
&self,
room_id: &RoomId,
kind: &EventType,
sender: &UserId,
state_key: Option<&str>,
content: serde_json::Value,
) -> Result<Vec<EventId>> {
let auth_events = state_res::auth_types_for_event(
kind.clone(),
sender,
state_key.map(|s| s.to_string()),
content,
);
let mut events = vec![];
for (event_type, state_key) in auth_events {
if let Some(state_key) = state_key.as_ref() {
if let Some(id) = self.room_state_get(room_id, &event_type, state_key)? {
events.push(id.event_id);
}
}
}
Ok(events)
}
// This fetches auth events from the current state using the
/// full `roomstateid_pdu` tree.
pub fn get_auth_events(
&self,
room_id: &RoomId,
kind: &EventType,
sender: &UserId,
state_key: Option<&str>,
content: serde_json::Value,
) -> Result<StateMap<PduEvent>> {
let auth_events = state_res::auth_types_for_event(
kind.clone(),
sender,
state_key.map(|s| s.to_string()),
content,
);
let mut events = StateMap::new();
for (event_type, state_key) in auth_events {
if let Some(s_key) = state_key.as_ref() {
if let Some(pdu) = self.room_state_get(room_id, &event_type, s_key)? {
events.insert((event_type, state_key), pdu);
}
}
}
Ok(events)
}
/// Generate a new StateHash.
///
/// A unique hash made from hashing the current states pduid's.
/// Because `append_state_pdu` handles the empty state db case it does not
/// have to be here.
fn new_state_hash_id(&self, room_id: &RoomId) -> Result<StateHashId> {
// Use hashed roomId as the first StateHash key for first state event in room
if self
.pduid_statehash
.scan_prefix(room_id.as_bytes())
.next()
.is_none()
{
// TODO use ring crate to hash
return Ok(room_id.as_str().to_owned());
}
let pdu_ids_to_hash = self
.pduid_statehash
.scan_prefix(room_id.as_bytes())
.values()
.next_back()
.unwrap() // We just checked if the tree was empty
.map(|hash| {
self.stateid_pduid
.scan_prefix(hash)
.values()
// pduid is roomId + count so just hash the whole thing
.map(|pid| Ok(pid?.to_vec()))
.collect::<Result<Vec<Vec<u8>>>>()
})??;
let mut hasher = DefaultHasher::new();
pdu_ids_to_hash.hash(&mut hasher);
let hash = hasher.finish().to_string();
// TODO not sure how you want to hash this
Ok(hash)
}
/// Checks if a room exists.
pub fn exists(&self, room_id: &RoomId) -> Result<bool> {
let mut prefix = room_id.to_string().as_bytes().to_vec();
@ -64,16 +330,20 @@ impl Rooms {
room_id: &RoomId,
) -> Result<HashMap<(EventType, String), PduEvent>> {
let mut hashmap = HashMap::new();
for pdu in self
.roomstateid_pdu
.scan_prefix(&room_id.to_string().as_bytes())
.values()
.map(|value| {
Ok::<_, Error>(
serde_json::from_slice::<PduEvent>(&value?)
for pdu in
self.roomstateid_pduid
.scan_prefix(&room_id.to_string().as_bytes())
.values()
.map(|value| {
Ok::<_, Error>(
serde_json::from_slice::<PduEvent>(
&self.pduid_pdu.get(value?)?.ok_or_else(|| {
Error::bad_database("PDU not found for ID in db.")
})?,
)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
)
})
)
})
{
let pdu = pdu?;
let state_key = pdu.state_key.clone().ok_or_else(|| {
@ -95,16 +365,20 @@ impl Rooms {
prefix.extend_from_slice(&event_type.to_string().as_bytes());
let mut hashmap = HashMap::new();
for pdu in self
.roomstateid_pdu
.scan_prefix(&prefix)
.values()
.map(|value| {
Ok::<_, Error>(
serde_json::from_slice::<PduEvent>(&value?)
for pdu in
self.roomstateid_pduid
.scan_prefix(&prefix)
.values()
.map(|value| {
Ok::<_, Error>(
serde_json::from_slice::<PduEvent>(
&self.pduid_pdu.get(value?)?.ok_or_else(|| {
Error::bad_database("PDU not found for ID in db.")
})?,
)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
)
})
)
})
{
let pdu = pdu?;
let state_key = pdu.state_key.clone().ok_or_else(|| {
@ -115,23 +389,28 @@ impl Rooms {
Ok(hashmap)
}
/// Returns the full room state.
/// Returns a single PDU in `room_id` with key (`event_type`, `state_key`).
pub fn room_state_get(
&self,
room_id: &RoomId,
event_type: &EventType,
state_key: &str,
) -> Result<Option<PduEvent>> {
let mut key = room_id.to_string().as_bytes().to_vec();
let mut key = room_id.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(&event_type.to_string().as_bytes());
key.push(0xff);
key.extend_from_slice(&state_key.as_bytes());
self.roomstateid_pdu.get(&key)?.map_or(Ok(None), |value| {
self.roomstateid_pduid.get(&key)?.map_or(Ok(None), |value| {
Ok::<_, Error>(Some(
serde_json::from_slice::<PduEvent>(&value)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
serde_json::from_slice::<PduEvent>(
&self
.pduid_pdu
.get(value)?
.ok_or_else(|| Error::bad_database("PDU not found for ID in db."))?,
)
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
))
})
}
@ -139,7 +418,7 @@ impl Rooms {
/// Returns the `count` of this pdu's id.
pub fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
self.eventid_pduid
.get(event_id.to_string().as_bytes())?
.get(event_id.as_bytes())?
.map_or(Ok(None), |pdu_id| {
Ok(Some(
utils::u64_from_bytes(
@ -153,7 +432,7 @@ impl Rooms {
/// Returns the json of a pdu.
pub fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<serde_json::Value>> {
self.eventid_pduid
.get(event_id.to_string().as_bytes())?
.get(event_id.as_bytes())?
.map_or(Ok(None), |pdu_id| {
Ok(Some(
serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or_else(|| {
@ -174,7 +453,7 @@ impl Rooms {
/// Returns the pdu.
pub fn get_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
self.eventid_pduid
.get(event_id.to_string().as_bytes())?
.get(event_id.as_bytes())?
.map_or(Ok(None), |pdu_id| {
Ok(Some(
serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or_else(|| {
@ -238,16 +517,15 @@ impl Rooms {
/// Replace the leaves of a room with a new event.
pub fn replace_pdu_leaves(&self, room_id: &RoomId, event_id: &EventId) -> Result<()> {
let mut prefix = room_id.to_string().as_bytes().to_vec();
let mut prefix = room_id.as_bytes().to_vec();
prefix.push(0xff);
for key in self.roomid_pduleaves.scan_prefix(&prefix).keys() {
self.roomid_pduleaves.remove(key?)?;
}
prefix.extend_from_slice(event_id.to_string().as_bytes());
self.roomid_pduleaves
.insert(&prefix, &*event_id.to_string())?;
prefix.extend_from_slice(event_id.as_bytes());
self.roomid_pduleaves.insert(&prefix, event_id.as_bytes())?;
Ok(())
}
@ -272,6 +550,14 @@ impl Rooms {
// TODO: Make sure this isn't called twice in parallel
let prev_events = self.get_pdu_leaves(&room_id)?;
let auth_events = self.get_auth_events(
&room_id,
&event_type,
&sender,
state_key.as_deref(),
content.clone(),
)?;
// Is the event authorized?
if let Some(state_key) = &state_key {
let power_levels = self
@ -333,138 +619,24 @@ impl Rooms {
// Don't allow encryption events when it's disabled
!globals.encryption_disabled()
}
EventType::RoomMember => {
let target_user_id = UserId::try_from(&**state_key).map_err(|_| {
Error::BadRequest(
ErrorKind::InvalidParam,
"State key of member event does not contain user id.",
)
})?;
let current_membership = self
.room_state_get(
&room_id,
&EventType::RoomMember,
&target_user_id.to_string(),
)?
.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
Ok(serde_json::from_value::<Raw<member::MemberEventContent>>(
pdu.content,
)
.expect("Raw::from_value always works.")
.deserialize()
.map_err(|_| Error::bad_database("Invalid Member event in db."))?
.membership)
})?;
let target_membership =
serde_json::from_value::<Raw<member::MemberEventContent>>(content.clone())
.expect("Raw::from_value always works.")
.deserialize()
.map_err(|_| Error::bad_database("Invalid Member event in db."))?
.membership;
let target_power = power_levels.users.get(&target_user_id).map_or_else(
|| {
if target_membership != member::MembershipState::Join {
None
} else {
Some(&power_levels.users_default)
}
},
// If it's okay, wrap with Some(_)
Some,
);
let join_rules =
self.room_state_get(&room_id, &EventType::RoomJoinRules, "")?
.map_or(Ok::<_, Error>(join_rules::JoinRule::Public), |pdu| {
Ok(serde_json::from_value::<
Raw<join_rules::JoinRulesEventContent>,
>(pdu.content)
.expect("Raw::from_value always works.")
.deserialize()
.map_err(|_| {
Error::bad_database("Database contains invalid JoinRules event")
})?
.join_rule)
})?;
if target_membership == member::MembershipState::Join {
let mut prev_events = prev_events.iter();
let prev_event = self
.get_pdu(prev_events.next().ok_or(Error::BadRequest(
ErrorKind::Unknown,
"Membership can't be the first event",
))?)?
.ok_or_else(|| {
Error::bad_database("PDU leaf points to invalid event!")
})?;
if prev_event.kind == EventType::RoomCreate
&& prev_event.prev_events.is_empty()
{
true
} else if sender != target_user_id {
false
} else if let member::MembershipState::Ban = current_membership {
false
} else {
join_rules == join_rules::JoinRule::Invite
&& (current_membership == member::MembershipState::Join
|| current_membership == member::MembershipState::Invite)
|| join_rules == join_rules::JoinRule::Public
}
} else if target_membership == member::MembershipState::Invite {
if let Some(third_party_invite_json) = content.get("third_party_invite") {
if current_membership == member::MembershipState::Ban {
false
} else {
let _third_party_invite =
serde_json::from_value::<member::ThirdPartyInvite>(
third_party_invite_json.clone(),
)
.map_err(|_| {
Error::BadRequest(
ErrorKind::InvalidParam,
"ThirdPartyInvite is invalid",
)
})?;
todo!("handle third party invites");
}
} else if sender_membership != member::MembershipState::Join
|| current_membership == member::MembershipState::Join
|| current_membership == member::MembershipState::Ban
{
false
} else {
sender_power
.filter(|&p| p >= &power_levels.invite)
.is_some()
}
} else if target_membership == member::MembershipState::Leave {
if sender == target_user_id {
current_membership == member::MembershipState::Join
|| current_membership == member::MembershipState::Invite
} else if sender_membership != member::MembershipState::Join
|| current_membership == member::MembershipState::Ban
&& sender_power.filter(|&p| p < &power_levels.ban).is_some()
{
false
} else {
sender_power.filter(|&p| p >= &power_levels.kick).is_some()
&& target_power < sender_power
}
} else if target_membership == member::MembershipState::Ban {
if sender_membership != member::MembershipState::Join {
false
} else {
sender_power.filter(|&p| p >= &power_levels.ban).is_some()
&& target_power < sender_power
}
} else {
false
}
}
EventType::RoomMember => event_auth::is_membership_change_allowed(
// TODO this is a bit of a hack but not sure how to have a type
// declared in `state_res` crate be
Requester {
prev_event_ids: prev_events.to_owned(),
room_id: &room_id,
content: &content,
state_key: Some(state_key.to_owned()),
sender: &sender,
},
&auth_events
.iter()
.map(|((ty, key), pdu)| {
Ok(((ty.clone(), key.clone()), pdu.convert_for_state_res()?))
})
.collect::<Result<StateMap<_>>>()?,
)
.ok_or(Error::Conflict("Found incoming PDU with invalid data."))?,
EventType::RoomCreate => prev_events.is_empty(),
// Not allow any of the following events if the sender is not joined.
_ if sender_membership != member::MembershipState::Join => false,
@ -474,7 +646,7 @@ impl Rooms {
>= &power_levels.state_default
}
} {
error!("Unauthorized");
error!("Unauthorized {}", event_type);
// Not authorized
return Err(Error::BadRequest(
ErrorKind::Forbidden,
@ -483,7 +655,7 @@ impl Rooms {
}
} else if !self.is_joined(&sender, &room_id)? {
// TODO: auth rules apply to all events, not only those with a state key
error!("Unauthorized");
error!("Unauthorized {}", event_type);
return Err(Error::BadRequest(
ErrorKind::Forbidden,
"Event is not authorized",
@ -524,7 +696,10 @@ impl Rooms {
depth: depth
.try_into()
.map_err(|_| Error::bad_database("Depth is invalid"))?,
auth_events: Vec::new(),
auth_events: auth_events
.into_iter()
.map(|(_, pdu)| pdu.event_id)
.collect(),
redacts: redacts.clone(),
unsigned,
hashes: ruma::events::pdu::EventHash {
@ -564,15 +739,19 @@ impl Rooms {
self.pduid_pdu.insert(&pdu_id, &*pdu_json.to_string())?;
self.eventid_pduid
.insert(pdu.event_id.to_string(), pdu_id.clone())?;
.insert(pdu.event_id.to_string(), &*pdu_id)?;
if let Some(state_key) = pdu.state_key {
let mut key = room_id.to_string().as_bytes().to_vec();
if let Some(state_key) = &pdu.state_key {
// We call this first because our StateHash relies on the
// state before the new event
self.append_state_pdu(&room_id, &pdu_id)?;
let mut key = room_id.as_bytes().to_vec();
key.push(0xff);
key.extend_from_slice(pdu.kind.to_string().as_bytes());
key.push(0xff);
key.extend_from_slice(state_key.as_bytes());
self.roomstateid_pdu.insert(key, &*pdu_json.to_string())?;
self.roomstateid_pduid.insert(key, pdu_id.as_slice())?;
}
match event_type {

View File

@ -2,7 +2,7 @@ use crate::{Error, Result};
use ruma::{
api::client::{
error::ErrorKind,
r0::uiaa::{AuthData, UiaaInfo},
r0::uiaa::{IncomingAuthData, UiaaInfo},
},
DeviceId, UserId,
};
@ -26,12 +26,12 @@ impl Uiaa {
&self,
user_id: &UserId,
device_id: &DeviceId,
auth: &AuthData,
auth: &IncomingAuthData,
uiaainfo: &UiaaInfo,
users: &super::users::Users,
globals: &super::globals::Globals,
) -> Result<(bool, UiaaInfo)> {
if let AuthData::DirectRequest {
if let IncomingAuthData::DirectRequest {
kind,
session,
auth_parameters,

View File

@ -177,6 +177,35 @@ impl PduEvent {
}
}
impl PduEvent {
pub fn convert_for_state_res(&self) -> Result<state_res::StateEvent> {
serde_json::from_value(json!({
"event_id": self.event_id,
"room_id": self.room_id,
"sender": self.sender,
"origin": self.origin,
"origin_server_ts": self.origin_server_ts,
"type": self.kind,
"content": self.content,
"state_key": self.state_key,
"prev_events": self.prev_events
.iter()
.map(|id| (id, EventHash { sha256: "hello".into() }))
.collect::<Vec<_>>(),
"depth": self.depth,
"auth_events": self.auth_events
.iter()
.map(|id| (id, EventHash { sha256: "hello".into() }))
.collect::<Vec<_>>(),
"redacts": self.redacts,
"unsigned": self.unsigned,
"hashes": self.hashes,
"signatures": self.signatures,
}))
.map_err(|_| Error::bad_database("Failed to convert PDU to ruma::Pdu type."))
}
}
/// Build the start of a PDU in order to add it to the `Database`.
#[derive(Debug)]
pub struct PduBuilder {

View File

@ -1,5 +1,8 @@
use crate::Error;
use ruma::identifiers::{DeviceId, UserId};
use ruma::{
api::IncomingRequest,
identifiers::{DeviceId, UserId},
};
use std::{convert::TryInto, ops::Deref};
#[cfg(feature = "conduit_bin")]
@ -16,13 +19,12 @@ use {
tokio::io::AsyncReadExt,
Request, State,
},
ruma::api::IncomingRequest,
std::io::Cursor,
};
/// This struct converts rocket requests into ruma structs by converting them into http requests
/// first.
pub struct Ruma<T> {
pub struct Ruma<T: IncomingRequest> {
pub body: T,
pub sender_id: Option<UserId>,
pub device_id: Option<Box<DeviceId>>,
@ -110,7 +112,7 @@ impl<'a, T: IncomingRequest> FromTransformedData<'a> for Ruma<T> {
}
}
impl<T> Deref for Ruma<T> {
impl<T: IncomingRequest> Deref for Ruma<T> {
type Target = T;
fn deref(&self) -> &Self::Target {

View File

@ -1,14 +1,17 @@
use crate::{client_server, ConduitResult, Database, Error, Result, Ruma};
use http::header::{HeaderValue, AUTHORIZATION};
use rocket::{get, post, put, response::content::Json, State};
use ruma::api::federation::{
directory::get_public_rooms,
discovery::{
get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
use ruma::api::{
client,
federation::{
directory::get_public_rooms,
discovery::{
get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
},
transactions::send_transaction_message,
},
transactions::send_transaction_message,
OutgoingRequest,
};
use ruma::api::{client, OutgoingRequest};
use serde_json::json;
use std::{
collections::BTreeMap,
@ -204,11 +207,11 @@ pub fn get_server_keys_deprecated(db: State<'_, Database>) -> Json<String> {
)]
pub async fn get_public_rooms_route(
db: State<'_, Database>,
body: Ruma<get_public_rooms::v1::Request>,
body: Ruma<get_public_rooms::v1::IncomingRequest>,
) -> ConduitResult<get_public_rooms::v1::Response> {
let Ruma {
body:
get_public_rooms::v1::Request {
get_public_rooms::v1::IncomingRequest {
room_network: _room_network, // TODO
limit,
since,
@ -229,7 +232,7 @@ pub async fn get_public_rooms_route(
body: client::r0::directory::get_public_rooms_filtered::IncomingRequest {
filter: None,
limit,
room_network: client::r0::directory::get_public_rooms_filtered::RoomNetwork::Matrix,
room_network: ruma::directory::RoomNetwork::Matrix,
server: None,
since,
},
@ -268,9 +271,9 @@ pub async fn get_public_rooms_route(
feature = "conduit_bin",
put("/_matrix/federation/v1/send/<_>", data = "<body>")
)]
pub fn send_transaction_message_route(
db: State<'_, Database>,
body: Ruma<send_transaction_message::v1::Request>,
pub fn send_transaction_message_route<'a>(
_db: State<'a, Database>,
body: Ruma<send_transaction_message::v1::IncomingRequest>,
) -> ConduitResult<send_transaction_message::v1::Response> {
dbg!(&*body);
Ok(send_transaction_message::v1::Response {

View File

@ -1,3 +1,4 @@
use crate::Error;
use argon2::{Config, Variant};
use cmp::Ordering;
use rand::prelude::*;
@ -90,3 +91,8 @@ pub fn common_elements(
.all(|b| b)
}))
}
pub fn deserialize<'de, T: serde::Deserialize<'de>>(val: &'de sled::IVec) -> Result<T, Error> {
serde_json::from_slice::<T>(val.as_ref())
.map_err(|_| Error::bad_database("PDU in db is invalid."))
}