From 489cbc0a930ab632a96e7fde6432875976f4d912 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Mon, 27 Jul 2020 17:36:54 +0200
Subject: [PATCH 1/9] refactor: use <_> instead of <_parameter_name>

---
 Cargo.lock            |  28 +-
 Cargo.toml            |   7 +-
 src/client_server.rs  | 670 ++++++++++++++++++------------------------
 src/database.rs       |  77 ++++-
 src/database/rooms.rs |  20 +-
 src/main.rs           |   2 +-
 src/ruma_wrapper.rs   |   6 +-
 7 files changed, 407 insertions(+), 403 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 45a5edd9..37a620b9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -275,6 +275,7 @@ dependencies = [
  "serde_json",
  "sled",
  "thiserror",
+ "tokio",
 ]
 
 [[package]]
@@ -1484,7 +1485,6 @@ dependencies = [
 [[package]]
 name = "rocket"
 version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket.git?rev=8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67#8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67"
 dependencies = [
  "async-trait",
  "atomic",
@@ -1509,7 +1509,6 @@ dependencies = [
 [[package]]
 name = "rocket_codegen"
 version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket.git?rev=8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67#8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67"
 dependencies = [
  "devise",
  "glob",
@@ -1521,7 +1520,6 @@ dependencies = [
 [[package]]
 name = "rocket_http"
 version = "0.5.0-dev"
-source = "git+https://github.com/SergioBenitez/Rocket.git?rev=8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67#8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67"
 dependencies = [
  "cookie",
  "http",
@@ -1543,7 +1541,7 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.1.0"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "ruma-api",
  "ruma-client-api",
@@ -1558,7 +1556,7 @@ dependencies = [
 [[package]]
 name = "ruma-api"
 version = "0.16.1"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "http",
  "percent-encoding",
@@ -1573,7 +1571,7 @@ dependencies = [
 [[package]]
 name = "ruma-api-macros"
 version = "0.16.1"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1583,7 +1581,7 @@ dependencies = [
 [[package]]
 name = "ruma-client-api"
 version = "0.9.0"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "http",
  "js_int",
@@ -1600,7 +1598,7 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.2.0"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "js_int",
  "ruma-serde",
@@ -1612,7 +1610,7 @@ dependencies = [
 [[package]]
 name = "ruma-events"
 version = "0.21.3"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -1627,7 +1625,7 @@ dependencies = [
 [[package]]
 name = "ruma-events-macros"
 version = "0.21.3"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1637,7 +1635,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.0.2"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -1652,7 +1650,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers"
 version = "0.17.1"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "rand",
  "serde",
@@ -1662,7 +1660,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-macros"
 version = "0.17.1"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1673,7 +1671,7 @@ dependencies = [
 [[package]]
 name = "ruma-serde"
 version = "0.2.2"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "form_urlencoded",
  "itoa",
@@ -1685,7 +1683,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.6.0-dev.1"
-source = "git+https://github.com/ruma/ruma?rev=e047c647ddcb368e7eb1e05ae8823a9494273457#e047c647ddcb368e7eb1e05ae8823a9494273457"
+source = "git+https://github.com/ruma/ruma?rev=d5d2d1d893fa12d27960e4c58d6c09b215d06e95#d5d2d1d893fa12d27960e4c58d6c09b215d06e95"
 dependencies = [
  "base64 0.12.3",
  "ring",
diff --git a/Cargo.toml b/Cargo.toml
index 02a90cdd..e5df8ddb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,8 +13,11 @@ edition = "2018"
 
 [dependencies]
 # TODO: This can become optional as soon as proper configs are supported
-rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"], optional = false } # Used to handle requests
-ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "e047c647ddcb368e7eb1e05ae8823a9494273457" } # Used for matrix spec type definitions and helpers
+#rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"] } # Used to handle requests
+rocket = { path = "../rocket/core/lib", features = ["tls"] }
+
+tokio = "0.2.22" # Used for long polling
+ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" } # Used for matrix spec type definitions and helpers
 sled = "0.32.0" # Used for storing data permanently
 log = "0.4.8" # Used for emitting log entries
 http = "0.2.1" # Used for rocket<->ruma conversions
diff --git a/src/client_server.rs b/src/client_server.rs
index c5bba03d..baeb8396 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -11,7 +11,7 @@ use log::warn;
 #[cfg(not(feature = "conduit_bin"))]
 use super::State;
 #[cfg(feature = "conduit_bin")]
-use rocket::{delete, get, options, post, put, State};
+use rocket::{delete, get, options, post, put, State, tokio};
 
 use ruma::{
     api::client::{
@@ -312,10 +312,10 @@ pub fn logout_route(
     db: State<'_, Database>,
     body: Ruma<logout::Request>,
 ) -> ConduitResult<logout::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
-    db.users.remove_device(&user_id, device_id)?;
+    db.users.remove_device(&sender_id, device_id)?;
 
     Ok(logout::Response.into())
 }
@@ -328,11 +328,11 @@ pub fn logout_all_route(
     db: State<'_, Database>,
     body: Ruma<logout_all::Request>,
 ) -> ConduitResult<logout_all::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    for device_id in db.users.all_device_ids(user_id) {
+    for device_id in db.users.all_device_ids(sender_id) {
         if let Ok(device_id) = device_id {
-            db.users.remove_device(&user_id, &device_id)?;
+            db.users.remove_device(&sender_id, &device_id)?;
         }
     }
 
@@ -347,7 +347,7 @@ pub fn change_password_route(
     db: State<'_, Database>,
     body: Ruma<change_password::Request>,
 ) -> ConduitResult<change_password::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     let mut uiaainfo = UiaaInfo {
@@ -363,29 +363,29 @@ pub fn change_password_route(
     if let Some(auth) = &body.auth {
         let (worked, uiaainfo) =
             db.uiaa
-                .try_auth(&user_id, device_id, auth, &uiaainfo, &db.users, &db.globals)?;
+                .try_auth(&sender_id, device_id, auth, &uiaainfo, &db.users, &db.globals)?;
         if !worked {
             return Err(Error::Uiaa(uiaainfo));
         }
     // Success!
     } else {
         uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
-        db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
+        db.uiaa.create(&sender_id, &device_id, &uiaainfo)?;
         return Err(Error::Uiaa(uiaainfo));
     }
 
-    db.users.set_password(&user_id, &body.new_password)?;
+    db.users.set_password(&sender_id, &body.new_password)?;
 
     // TODO: Read logout_devices field when it's available and respect that, currently not supported in Ruma
     // See: https://github.com/ruma/ruma/issues/107
     // Logout all devices except the current one
     for id in db
         .users
-        .all_device_ids(&user_id)
+        .all_device_ids(&sender_id)
         .filter_map(|id| id.ok())
         .filter(|id| id != device_id)
     {
-        db.users.remove_device(&user_id, &id)?;
+        db.users.remove_device(&sender_id, &id)?;
     }
 
     Ok(change_password::Response.into())
@@ -399,7 +399,7 @@ pub fn deactivate_route(
     db: State<'_, Database>,
     body: Ruma<deactivate::Request>,
 ) -> ConduitResult<deactivate::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     let mut uiaainfo = UiaaInfo {
@@ -414,7 +414,7 @@ pub fn deactivate_route(
 
     if let Some(auth) = &body.auth {
         let (worked, uiaainfo) = db.uiaa.try_auth(
-            &user_id,
+            &sender_id,
             &device_id,
             auth,
             &uiaainfo,
@@ -427,15 +427,15 @@ pub fn deactivate_route(
     // Success!
     } else {
         uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
-        db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
+        db.uiaa.create(&sender_id, &device_id, &uiaainfo)?;
         return Err(Error::Uiaa(uiaainfo));
     }
 
     // Leave all joined rooms and reject all invitations
     for room_id in db
         .rooms
-        .rooms_joined(&user_id)
-        .chain(db.rooms.rooms_invited(&user_id))
+        .rooms_joined(&sender_id)
+        .chain(db.rooms.rooms_invited(&sender_id))
     {
         let room_id = room_id?;
         let event = member::MemberEventContent {
@@ -448,18 +448,18 @@ pub fn deactivate_route(
 
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomMember,
             serde_json::to_value(event).expect("event is valid, we just created it"),
             None,
-            Some(user_id.to_string()),
+            Some(sender_id.to_string()),
             None,
             &db.globals,
         )?;
     }
 
     // Remove devices and mark account as deactivated
-    db.users.deactivate_account(&user_id)?;
+    db.users.deactivate_account(&sender_id)?;
 
     Ok(deactivate::Response {
         id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
@@ -500,11 +500,11 @@ pub fn get_pushrules_all_route(
     db: State<'_, Database>,
     body: Ruma<get_pushrules_all::Request>,
 ) -> ConduitResult<get_pushrules_all::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let event = db
         .account_data
-        .get::<ruma::events::push_rules::PushRulesEvent>(None, &user_id, EventType::PushRules)?
+        .get::<ruma::events::push_rules::PushRulesEvent>(None, &sender_id, EventType::PushRules)?
         .ok_or(Error::BadRequest(
             ErrorKind::NotFound,
             "PushRules event not found.",
@@ -517,15 +517,12 @@ pub fn get_pushrules_all_route(
 }
 
 #[cfg_attr(feature = "conduit_bin", put(
-    "/_matrix/client/r0/pushrules/<_scope>/<_kind>/<_rule_id>",
+    "/_matrix/client/r0/pushrules/<_>/<_>/<_>",
     //data = "<body>"
 ))]
 pub fn set_pushrule_route(
     //db: State<'_, Database>,
     //body: Ruma<set_pushrule::Request>,
-    _scope: String,
-    _kind: String,
-    _rule_id: String,
 ) -> ConduitResult<set_pushrule::Response> {
     // TODO
     warn!("TODO: set_pushrule_route");
@@ -534,12 +531,9 @@ pub fn set_pushrule_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/pushrules/<_scope>/<_kind>/<_rule_id>/enabled")
+    put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled")
 )]
 pub fn set_pushrule_enabled_route(
-    _scope: String,
-    _kind: String,
-    _rule_id: String,
 ) -> ConduitResult<set_pushrule_enabled::Response> {
     // TODO
     warn!("TODO: set_pushrule_enabled_route");
@@ -548,11 +542,9 @@ pub fn set_pushrule_enabled_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/user/<_user_id>/filter/<_filter_id>")
+    get("/_matrix/client/r0/user/<_>/filter/<_>")
 )]
 pub fn get_filter_route(
-    _user_id: String,
-    _filter_id: String,
 ) -> ConduitResult<get_filter::Response> {
     // TODO
     Ok(get_filter::Response {
@@ -569,9 +561,9 @@ pub fn get_filter_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/user/<_user_id>/filter")
+    post("/_matrix/client/r0/user/<_>/filter")
 )]
-pub fn create_filter_route(_user_id: String) -> ConduitResult<create_filter::Response> {
+pub fn create_filter_route() -> ConduitResult<create_filter::Response> {
     // TODO
     Ok(create_filter::Response {
         filter_id: utils::random_string(10),
@@ -582,17 +574,15 @@ pub fn create_filter_route(_user_id: String) -> ConduitResult<create_filter::Res
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/user/<_user_id>/account_data/<_type>",
+        "/_matrix/client/r0/user/<_>/account_data/<_>",
         data = "<body>"
     )
 )]
 pub fn set_global_account_data_route(
     db: State<'_, Database>,
     body: Ruma<set_global_account_data::Request>,
-    _user_id: String,
-    _type: String,
 ) -> ConduitResult<set_global_account_data::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let content = serde_json::from_str::<serde_json::Value>(body.data.get())
         .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
@@ -601,7 +591,7 @@ pub fn set_global_account_data_route(
 
     db.account_data.update(
         None,
-        user_id,
+        sender_id,
         event_type.clone().into(),
         &BasicEvent {
             content: CustomEventContent {
@@ -618,23 +608,21 @@ pub fn set_global_account_data_route(
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/user/<_user_id>/account_data/<_type>",
+        "/_matrix/client/r0/user/<_>/account_data/<_>",
         data = "<body>"
     )
 )]
 pub fn get_global_account_data_route(
     db: State<'_, Database>,
     body: Ruma<get_global_account_data::Request>,
-    _user_id: String,
-    _type: String,
 ) -> ConduitResult<get_global_account_data::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let data = db
         .account_data
         .get::<ruma::events::AnyBasicEvent>(
             None,
-            user_id,
+            sender_id,
             EventType::try_from(&body.event_type).expect("EventType::try_from can never fail"),
         )?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
@@ -647,30 +635,29 @@ pub fn get_global_account_data_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/profile/<_user_id>/displayname", data = "<body>")
+    put("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
 )]
 pub fn set_displayname_route(
     db: State<'_, Database>,
     body: Ruma<set_display_name::Request>,
-    _user_id: String,
 ) -> ConduitResult<set_display_name::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     db.users
-        .set_displayname(&user_id, body.displayname.clone())?;
+        .set_displayname(&sender_id, body.displayname.clone())?;
 
     // Send a new membership event into all joined rooms
-    for room_id in db.rooms.rooms_joined(&user_id) {
+    for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomMember,
             serde_json::to_value(ruma::events::room::member::MemberEventContent {
                 displayname: body.displayname.clone(),
                 ..serde_json::from_value::<Raw<_>>(
                     db.rooms
-                        .room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
+                        .room_state_get(&room_id, &EventType::RoomMember, &sender_id.to_string())?
                         .ok_or_else(|| {
                             Error::bad_database(
                                 "Tried to send displayname update for user not in the room.",
@@ -685,7 +672,7 @@ pub fn set_displayname_route(
             })
             .expect("event is valid, we just created it"),
             None,
-            Some(user_id.to_string()),
+            Some(sender_id.to_string()),
             None,
             &db.globals,
         )?;
@@ -695,9 +682,9 @@ pub fn set_displayname_route(
     db.global_edus.update_presence(
         ruma::events::presence::PresenceEvent {
             content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&user_id)?,
+                avatar_url: db.users.avatar_url(&sender_id)?,
                 currently_active: None,
-                displayname: db.users.displayname(&user_id)?,
+                displayname: db.users.displayname(&sender_id)?,
                 last_active_ago: Some(
                     utils::millis_since_unix_epoch()
                         .try_into()
@@ -706,7 +693,7 @@ pub fn set_displayname_route(
                 presence: ruma::presence::PresenceState::Online,
                 status_msg: None,
             },
-            sender: user_id.clone(),
+            sender: sender_id.clone(),
         },
         &db.globals,
     )?;
@@ -716,30 +703,27 @@ pub fn set_displayname_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/profile/<_user_id>/displayname", data = "<body>")
+    get("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
 )]
 pub fn get_displayname_route(
     db: State<'_, Database>,
     body: Ruma<get_display_name::Request>,
-    _user_id: String,
 ) -> ConduitResult<get_display_name::Response> {
-    let user_id = body.body.user_id.clone();
     Ok(get_display_name::Response {
-        displayname: db.users.displayname(&user_id)?,
+        displayname: db.users.displayname(&body.user_id)?,
     }
     .into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/profile/<_user_id>/avatar_url", data = "<body>")
+    put("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
 )]
 pub fn set_avatar_url_route(
     db: State<'_, Database>,
     body: Ruma<set_avatar_url::Request>,
-    _user_id: String,
 ) -> ConduitResult<set_avatar_url::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     if let Some(avatar_url) = &body.avatar_url {
         if !avatar_url.starts_with("mxc://") {
@@ -753,20 +737,20 @@ pub fn set_avatar_url_route(
         // TODO also make sure this is valid mxc:// format (not only starting with it)
     }
 
-    db.users.set_avatar_url(&user_id, body.avatar_url.clone())?;
+    db.users.set_avatar_url(&sender_id, body.avatar_url.clone())?;
 
     // Send a new membership event into all joined rooms
-    for room_id in db.rooms.rooms_joined(&user_id) {
+    for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomMember,
             serde_json::to_value(ruma::events::room::member::MemberEventContent {
                 avatar_url: body.avatar_url.clone(),
                 ..serde_json::from_value::<Raw<_>>(
                     db.rooms
-                        .room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
+                        .room_state_get(&room_id, &EventType::RoomMember, &sender_id.to_string())?
                         .ok_or_else(|| {
                             Error::bad_database(
                                 "Tried to send avatar url update for user not in the room.",
@@ -781,7 +765,7 @@ pub fn set_avatar_url_route(
             })
             .expect("event is valid, we just created it"),
             None,
-            Some(user_id.to_string()),
+            Some(sender_id.to_string()),
             None,
             &db.globals,
         )?;
@@ -791,9 +775,9 @@ pub fn set_avatar_url_route(
     db.global_edus.update_presence(
         ruma::events::presence::PresenceEvent {
             content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&user_id)?,
+                avatar_url: db.users.avatar_url(&sender_id)?,
                 currently_active: None,
-                displayname: db.users.displayname(&user_id)?,
+                displayname: db.users.displayname(&sender_id)?,
                 last_active_ago: Some(
                     utils::millis_since_unix_epoch()
                         .try_into()
@@ -802,7 +786,7 @@ pub fn set_avatar_url_route(
                 presence: ruma::presence::PresenceState::Online,
                 status_msg: None,
             },
-            sender: user_id.clone(),
+            sender: sender_id.clone(),
         },
         &db.globals,
     )?;
@@ -812,32 +796,28 @@ pub fn set_avatar_url_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/profile/<_user_id>/avatar_url", data = "<body>")
+    get("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
 )]
 pub fn get_avatar_url_route(
     db: State<'_, Database>,
     body: Ruma<get_avatar_url::Request>,
-    _user_id: String,
 ) -> ConduitResult<get_avatar_url::Response> {
-    let user_id = body.body.user_id.clone();
     Ok(get_avatar_url::Response {
-        avatar_url: db.users.avatar_url(&user_id)?,
+        avatar_url: db.users.avatar_url(&body.user_id)?,
     }
     .into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/profile/<_user_id>", data = "<body>")
+    get("/_matrix/client/r0/profile/<_>", data = "<body>")
 )]
 pub fn get_profile_route(
     db: State<'_, Database>,
     body: Ruma<get_profile::Request>,
-    _user_id: String,
 ) -> ConduitResult<get_profile::Response> {
-    let user_id = body.body.user_id.clone();
-    let avatar_url = db.users.avatar_url(&user_id)?;
-    let displayname = db.users.displayname(&user_id)?;
+    let avatar_url = db.users.avatar_url(&body.user_id)?;
+    let displayname = db.users.displayname(&body.user_id)?;
 
     if avatar_url.is_none() && displayname.is_none() {
         // Return 404 if we don't have a profile for this id
@@ -856,21 +836,20 @@ pub fn get_profile_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/presence/<_user_id>/status", data = "<body>")
+    put("/_matrix/client/r0/presence/<_>/status", data = "<body>")
 )]
 pub fn set_presence_route(
     db: State<'_, Database>,
     body: Ruma<set_presence::Request>,
-    _user_id: String,
 ) -> ConduitResult<set_presence::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     db.global_edus.update_presence(
         ruma::events::presence::PresenceEvent {
             content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&user_id)?,
+                avatar_url: db.users.avatar_url(&sender_id)?,
                 currently_active: None,
-                displayname: db.users.displayname(&user_id)?,
+                displayname: db.users.displayname(&sender_id)?,
                 last_active_ago: Some(
                     utils::millis_since_unix_epoch()
                         .try_into()
@@ -879,7 +858,7 @@ pub fn set_presence_route(
                 presence: body.presence,
                 status_msg: body.status_msg.clone(),
             },
-            sender: user_id.clone(),
+            sender: sender_id.clone(),
         },
         &db.globals,
     )?;
@@ -895,26 +874,26 @@ pub fn upload_keys_route(
     db: State<'_, Database>,
     body: Ruma<upload_keys::Request>,
 ) -> ConduitResult<upload_keys::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     if let Some(one_time_keys) = &body.one_time_keys {
         for (key_key, key_value) in one_time_keys {
             db.users
-                .add_one_time_key(user_id, device_id, key_key, key_value)?;
+                .add_one_time_key(sender_id, device_id, key_key, key_value)?;
         }
     }
 
     if let Some(device_keys) = &body.device_keys {
         // This check is needed to assure that signatures are kept
-        if db.users.get_device_keys(user_id, device_id)?.is_none() {
+        if db.users.get_device_keys(sender_id, device_id)?.is_none() {
             db.users
-                .add_device_keys(user_id, device_id, device_keys, &db.globals)?;
+                .add_device_keys(sender_id, device_id, device_keys, &db.globals)?;
         }
     }
 
     Ok(upload_keys::Response {
-        one_time_key_counts: db.users.count_one_time_keys(user_id, device_id)?,
+        one_time_key_counts: db.users.count_one_time_keys(sender_id, device_id)?,
     }
     .into())
 }
@@ -927,7 +906,7 @@ pub fn get_keys_route(
     db: State<'_, Database>,
     body: Ruma<get_keys::Request>,
 ) -> ConduitResult<get_keys::Response> {
-    let sender_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut master_keys = BTreeMap::new();
     let mut self_signing_keys = BTreeMap::new();
@@ -1038,10 +1017,10 @@ pub fn create_backup_route(
     db: State<'_, Database>,
     body: Ruma<create_backup::Request>,
 ) -> ConduitResult<create_backup::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let version = db
         .key_backups
-        .create_backup(&user_id, &body.algorithm, &db.globals)?;
+        .create_backup(&sender_id, &body.algorithm, &db.globals)?;
 
     Ok(create_backup::Response { version }.into())
 }
@@ -1049,18 +1028,17 @@ pub fn create_backup_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/unstable/room_keys/version/<_version>",
+        "/_matrix/client/unstable/room_keys/version/<_>",
         data = "<body>"
     )
 )]
 pub fn update_backup_route(
     db: State<'_, Database>,
     body: Ruma<update_backup::Request>,
-    _version: String,
 ) -> ConduitResult<update_backup::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     db.key_backups
-        .update_backup(&user_id, &body.version, &body.algorithm, &db.globals)?;
+        .update_backup(&sender_id, &body.version, &body.algorithm, &db.globals)?;
 
     Ok(update_backup::Response.into())
 }
@@ -1073,11 +1051,11 @@ pub fn get_latest_backup_route(
     db: State<'_, Database>,
     body: Ruma<get_latest_backup::Request>,
 ) -> ConduitResult<get_latest_backup::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let (version, algorithm) =
         db.key_backups
-            .get_latest_backup(&user_id)?
+            .get_latest_backup(&sender_id)?
             .ok_or(Error::BadRequest(
                 ErrorKind::NotFound,
                 "Key backup does not exist.",
@@ -1085,8 +1063,8 @@ pub fn get_latest_backup_route(
 
     Ok(get_latest_backup::Response {
         algorithm,
-        count: (db.key_backups.count_keys(user_id, &version)? as u32).into(),
-        etag: db.key_backups.get_etag(user_id, &version)?,
+        count: (db.key_backups.count_keys(sender_id, &version)? as u32).into(),
+        etag: db.key_backups.get_etag(sender_id, &version)?,
         version,
     }
     .into())
@@ -1095,19 +1073,18 @@ pub fn get_latest_backup_route(
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/unstable/room_keys/version/<_version>",
+        "/_matrix/client/unstable/room_keys/version/<_>",
         data = "<body>"
     )
 )]
 pub fn get_backup_route(
     db: State<'_, Database>,
     body: Ruma<get_backup::Request>,
-    _version: String,
 ) -> ConduitResult<get_backup::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let algorithm =
         db.key_backups
-            .get_backup(&user_id, &body.version)?
+            .get_backup(&sender_id, &body.version)?
             .ok_or(Error::BadRequest(
                 ErrorKind::NotFound,
                 "Key backup does not exist.",
@@ -1115,8 +1092,8 @@ pub fn get_backup_route(
 
     Ok(get_backup::Response {
         algorithm,
-        count: (db.key_backups.count_keys(user_id, &body.version)? as u32).into(),
-        etag: db.key_backups.get_etag(user_id, &body.version)?,
+        count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(),
+        etag: db.key_backups.get_etag(sender_id, &body.version)?,
         version: body.version.clone(),
     }
     .into())
@@ -1131,12 +1108,12 @@ pub fn add_backup_keys_route(
     db: State<'_, Database>,
     body: Ruma<add_backup_keys::Request>,
 ) -> ConduitResult<add_backup_keys::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     for (room_id, room) in &body.rooms {
         for (session_id, key_data) in &room.sessions {
             db.key_backups.add_key(
-                &user_id,
+                &sender_id,
                 &body.version,
                 &room_id,
                 &session_id,
@@ -1147,8 +1124,8 @@ pub fn add_backup_keys_route(
     }
 
     Ok(add_backup_keys::Response {
-        count: (db.key_backups.count_keys(user_id, &body.version)? as u32).into(),
-        etag: db.key_backups.get_etag(user_id, &body.version)?,
+        count: (db.key_backups.count_keys(sender_id, &body.version)? as u32).into(),
+        etag: db.key_backups.get_etag(sender_id, &body.version)?,
     }
     .into())
 }
@@ -1161,23 +1138,22 @@ pub fn get_backup_keys_route(
     db: State<'_, Database>,
     body: Ruma<get_backup_keys::Request>,
 ) -> ConduitResult<get_backup_keys::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    let rooms = db.key_backups.get_all(&user_id, &body.version)?;
+    let rooms = db.key_backups.get_all(&sender_id, &body.version)?;
 
     Ok(get_backup_keys::Response { rooms }.into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/read_markers", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/read_markers", data = "<body>")
 )]
 pub fn set_read_marker_route(
     db: State<'_, Database>,
     body: Ruma<set_read_marker::Request>,
-    _room_id: String,
 ) -> ConduitResult<set_read_marker::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let fully_read_event = ruma::events::fully_read::FullyReadEvent {
         content: ruma::events::fully_read::FullyReadEventContent {
@@ -1187,7 +1163,7 @@ pub fn set_read_marker_route(
     };
     db.account_data.update(
         Some(&body.room_id),
-        &user_id,
+        &sender_id,
         EventType::FullyRead,
         &fully_read_event,
         &db.globals,
@@ -1196,7 +1172,7 @@ pub fn set_read_marker_route(
     if let Some(event) = &body.read_receipt {
         db.rooms.edus.room_read_set(
             &body.room_id,
-            &user_id,
+            &sender_id,
             db.rooms.get_pdu_count(event)?.ok_or(Error::BadRequest(
                 ErrorKind::InvalidParam,
                 "Event does not exist.",
@@ -1205,7 +1181,7 @@ pub fn set_read_marker_route(
 
         let mut user_receipts = BTreeMap::new();
         user_receipts.insert(
-            user_id.clone(),
+            sender_id.clone(),
             ruma::events::receipt::Receipt {
                 ts: Some(SystemTime::now()),
             },
@@ -1219,7 +1195,7 @@ pub fn set_read_marker_route(
         );
 
         db.rooms.edus.roomlatest_update(
-            &user_id,
+            &sender_id,
             &body.room_id,
             AnyEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(
                 ruma::events::receipt::ReceiptEvent {
@@ -1236,21 +1212,19 @@ pub fn set_read_marker_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/rooms/<_room_id>/typing/<_user_id>",
+        "/_matrix/client/r0/rooms/<_>/typing/<_>",
         data = "<body>"
     )
 )]
 pub fn create_typing_event_route(
     db: State<'_, Database>,
     body: Ruma<create_typing_event::Request>,
-    _room_id: String,
-    _user_id: String,
 ) -> ConduitResult<create_typing_event::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     if body.typing {
         db.rooms.edus.roomactive_add(
-            &user_id,
+            &sender_id,
             &body.room_id,
             body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000)
                 + utils::millis_since_unix_epoch(),
@@ -1259,7 +1233,7 @@ pub fn create_typing_event_route(
     } else {
         db.rooms
             .edus
-            .roomactive_remove(&user_id, &body.room_id, &db.globals)?;
+            .roomactive_remove(&sender_id, &body.room_id, &db.globals)?;
     }
 
     Ok(create_typing_event::Response.into())
@@ -1273,7 +1247,7 @@ pub fn create_room_route(
     db: State<'_, Database>,
     body: Ruma<create_room::Request>,
 ) -> ConduitResult<create_room::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let room_id = RoomId::new(db.globals.server_name());
 
@@ -1296,7 +1270,7 @@ pub fn create_room_route(
             }
         })?;
 
-    let mut content = ruma::events::room::create::CreateEventContent::new(user_id.clone());
+    let mut content = ruma::events::room::create::CreateEventContent::new(sender_id.clone());
     content.federate = body.creation_content.as_ref().map_or(true, |c| c.federate);
     content.predecessor = body
         .creation_content
@@ -1307,7 +1281,7 @@ pub fn create_room_route(
     // 1. The room create event
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomCreate,
         serde_json::to_value(content).expect("event is valid, we just created it"),
         None,
@@ -1319,18 +1293,18 @@ pub fn create_room_route(
     // 2. Let the room creator join
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(member::MemberEventContent {
             membership: member::MembershipState::Join,
-            displayname: db.users.displayname(&user_id)?,
-            avatar_url: db.users.avatar_url(&user_id)?,
+            displayname: db.users.displayname(&sender_id)?,
+            avatar_url: db.users.avatar_url(&sender_id)?,
             is_direct: body.is_direct,
             third_party_invite: None,
         })
         .expect("event is valid, we just created it"),
         None,
-        Some(user_id.to_string()),
+        Some(sender_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1344,9 +1318,9 @@ pub fn create_room_route(
 
     // 3. Power levels
     let mut users = BTreeMap::new();
-    users.insert(user_id.clone(), 100.into());
-    for invite_user_id in &body.invite {
-        users.insert(invite_user_id.clone(), 100.into());
+    users.insert(sender_id.clone(), 100.into());
+    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 {
@@ -1372,7 +1346,7 @@ pub fn create_room_route(
     };
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomPowerLevels,
         power_levels_content,
         None,
@@ -1385,7 +1359,7 @@ pub fn create_room_route(
     // 4.1 Join Rules
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomJoinRules,
         match preset {
             create_room::RoomPreset::PublicChat => serde_json::to_value(
@@ -1407,7 +1381,7 @@ pub fn create_room_route(
     // 4.2 History Visibility
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomHistoryVisibility,
         serde_json::to_value(history_visibility::HistoryVisibilityEventContent::new(
             history_visibility::HistoryVisibility::Shared,
@@ -1422,7 +1396,7 @@ pub fn create_room_route(
     // 4.3 Guest Access
     db.rooms.append_pdu(
         room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomGuestAccess,
         match preset {
             create_room::RoomPreset::PublicChat => serde_json::to_value(
@@ -1454,7 +1428,7 @@ pub fn create_room_route(
 
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             event_type.clone(),
             serde_json::from_str(content.get()).map_err(|_| {
                 Error::BadRequest(ErrorKind::BadJson, "Invalid initial_state content.")
@@ -1470,7 +1444,7 @@ pub fn create_room_route(
     if let Some(name) = &body.name {
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomName,
             serde_json::to_value(
                 name::NameEventContent::new(name.clone())
@@ -1487,7 +1461,7 @@ pub fn create_room_route(
     if let Some(topic) = &body.topic {
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomTopic,
             serde_json::to_value(topic::TopicEventContent {
                 topic: topic.clone(),
@@ -1504,7 +1478,7 @@ pub fn create_room_route(
     for user in &body.invite {
         db.rooms.append_pdu(
             room_id.clone(),
-            user_id.clone(),
+            sender_id.clone(),
             EventType::RoomMember,
             serde_json::to_value(member::MemberEventContent {
                 membership: member::MembershipState::Invite,
@@ -1541,12 +1515,12 @@ pub fn joined_rooms_route(
     db: State<'_, Database>,
     body: Ruma<joined_rooms::Request>,
 ) -> ConduitResult<joined_rooms::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     Ok(joined_rooms::Response {
         joined_rooms: db
             .rooms
-            .rooms_joined(&user_id)
+            .rooms_joined(&sender_id)
             .filter_map(|r| r.ok())
             .collect(),
     }
@@ -1556,22 +1530,19 @@ pub fn joined_rooms_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/rooms/<_room_id>/redact/<_event_id>/<_txn_id>",
+        "/_matrix/client/r0/rooms/<_>/redact/<_>/<_>",
         data = "<body>"
     )
 )]
 pub fn redact_event_route(
     db: State<'_, Database>,
     body: Ruma<redact_event::Request>,
-    _room_id: String,
-    _event_id: String,
-    _txn_id: String,
 ) -> ConduitResult<redact_event::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let event_id = db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomRedaction,
         serde_json::to_value(redaction::RedactionEventContent {
             reason: body.reason.clone(),
@@ -1588,12 +1559,11 @@ pub fn redact_event_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/directory/room/<_room_alias>", data = "<body>")
+    put("/_matrix/client/r0/directory/room/<_>", data = "<body>")
 )]
 pub fn create_alias_route(
     db: State<'_, Database>,
     body: Ruma<create_alias::Request>,
-    _room_alias: String,
 ) -> ConduitResult<create_alias::Response> {
     if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
         return Err(Error::Conflict("Alias already exists."));
@@ -1607,12 +1577,11 @@ pub fn create_alias_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    delete("/_matrix/client/r0/directory/room/<_room_alias>", data = "<body>")
+    delete("/_matrix/client/r0/directory/room/<_>", data = "<body>")
 )]
 pub fn delete_alias_route(
     db: State<'_, Database>,
     body: Ruma<delete_alias::Request>,
-    _room_alias: String,
 ) -> ConduitResult<delete_alias::Response> {
     db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
 
@@ -1621,12 +1590,11 @@ pub fn delete_alias_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/directory/room/<_room_alias>", data = "<body>")
+    get("/_matrix/client/r0/directory/room/<_>", data = "<body>")
 )]
 pub fn get_alias_route(
     db: State<'_, Database>,
     body: Ruma<get_alias::Request>,
-    _room_alias: String,
 ) -> ConduitResult<get_alias::Response> {
     if body.room_alias.server_name() != db.globals.server_name() {
         todo!("ask remote server");
@@ -1649,32 +1617,31 @@ pub fn get_alias_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/join", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/join", data = "<body>")
 )]
 pub fn join_room_by_id_route(
     db: State<'_, Database>,
     body: Ruma<join_room_by_id::Request>,
-    _room_id: String,
 ) -> ConduitResult<join_room_by_id::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     // TODO: Ask a remote server if we don't have this room
 
     let event = member::MemberEventContent {
         membership: member::MembershipState::Join,
-        displayname: db.users.displayname(&user_id)?,
-        avatar_url: db.users.avatar_url(&user_id)?,
+        displayname: db.users.displayname(&sender_id)?,
+        avatar_url: db.users.avatar_url(&sender_id)?,
         is_direct: None,
         third_party_invite: None,
     };
 
     db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(event).expect("event is valid, we just created it"),
         None,
-        Some(user_id.to_string()),
+        Some(sender_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1687,12 +1654,11 @@ pub fn join_room_by_id_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/join/<_room_id_or_alias>", data = "<body>")
+    post("/_matrix/client/r0/join/<_>", data = "<body>")
 )]
 pub fn join_room_by_id_or_alias_route(
     db: State<'_, Database>,
     body: Ruma<join_room_by_id_or_alias::Request>,
-    _room_id_or_alias: String,
 ) -> ConduitResult<join_room_by_id_or_alias::Response> {
     let room_id = RoomId::try_from(body.room_id_or_alias.clone()).or_else(|alias| {
         Ok::<_, Error>(db.rooms.id_from_alias(&alias)?.ok_or(Error::BadRequest(
@@ -1702,7 +1668,7 @@ pub fn join_room_by_id_or_alias_route(
     })?;
 
     let body = Ruma {
-        user_id: body.user_id.clone(),
+        sender_id: body.sender_id.clone(),
         device_id: body.device_id.clone(),
         json_body: None,
         body: join_room_by_id::Request {
@@ -1712,25 +1678,24 @@ pub fn join_room_by_id_or_alias_route(
     };
 
     Ok(join_room_by_id_or_alias::Response {
-        room_id: join_room_by_id_route(db, body, "".to_owned())?.0.room_id,
+        room_id: join_room_by_id_route(db, body)?.0.room_id,
     }
     .into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/leave", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/leave", data = "<body>")
 )]
 pub fn leave_room_route(
     db: State<'_, Database>,
     body: Ruma<leave_room::Request>,
-    _room_id: String,
 ) -> ConduitResult<leave_room::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
+            .room_state_get(&body.room_id, &EventType::RoomMember, &sender_id.to_string())?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot leave a room you are not a member of.",
@@ -1746,11 +1711,11 @@ pub fn leave_room_route(
 
     db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(event).expect("event is valid, we just created it"),
         None,
-        Some(user_id.to_string()),
+        Some(sender_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1760,18 +1725,17 @@ pub fn leave_room_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/kick", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/kick", data = "<body>")
 )]
 pub fn kick_user_route(
     db: State<'_, Database>,
     body: Ruma<kick_user::Request>,
-    _room_id: String,
 ) -> ConduitResult<kick_user::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut event = serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
+            .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot kick member that's not in the room.",
@@ -1779,7 +1743,7 @@ pub fn kick_user_route(
             .content
             .clone(),
     )
-    .map_err(|_| Error::bad_database("Invalid member event in database."))?
+    .expect("Raw::from_value always works")
     .deserialize()
     .map_err(|_| Error::bad_database("Invalid member event in database."))?;
 
@@ -1788,11 +1752,11 @@ pub fn kick_user_route(
 
     db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(), // Sender
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(event).expect("event is valid, we just created it"),
         None,
-        Some(body.body.user_id.to_string()),
+        Some(body.user_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1802,16 +1766,15 @@ pub fn kick_user_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/rooms/<_room_id>/joined_members", data = "<body>")
+    get("/_matrix/client/r0/rooms/<_>/joined_members", data = "<body>")
 )]
 pub fn joined_members_route(
     db: State<'_, Database>,
     body: Ruma<joined_members::Request>,
-    _room_id: String,
 ) -> ConduitResult<joined_members::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(&user_id, &body.room_id).unwrap_or(false) {
+    if !db.rooms.is_joined(&sender_id, &body.room_id).unwrap_or(false) {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You aren't a member of the room.",
@@ -1837,25 +1800,24 @@ pub fn joined_members_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/ban", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/ban", data = "<body>")
 )]
 pub fn ban_user_route(
     db: State<'_, Database>,
     body: Ruma<ban_user::Request>,
-    _room_id: String,
 ) -> ConduitResult<ban_user::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     // TODO: reason
 
     let event = db
         .rooms
-        .room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
+        .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
         .map_or(
             Ok::<_, Error>(member::MemberEventContent {
                 membership: member::MembershipState::Ban,
-                displayname: db.users.displayname(&user_id)?,
-                avatar_url: db.users.avatar_url(&user_id)?,
+                displayname: db.users.displayname(&body.user_id)?,
+                avatar_url: db.users.avatar_url(&body.user_id)?,
                 is_direct: None,
                 third_party_invite: None,
             }),
@@ -1863,7 +1825,7 @@ pub fn ban_user_route(
                 let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
                     event.content.clone(),
                 )
-                .map_err(|_| Error::bad_database("Invalid member event in database."))?
+                .expect("Raw::from_value always works")
                 .deserialize()
                 .map_err(|_| Error::bad_database("Invalid member event in database."))?;
                 event.membership = ruma::events::room::member::MembershipState::Ban;
@@ -1873,11 +1835,11 @@ pub fn ban_user_route(
 
     db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(), // Sender
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(event).expect("event is valid, we just created it"),
         None,
-        Some(body.body.user_id.to_string()),
+        Some(body.user_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1887,18 +1849,17 @@ pub fn ban_user_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/unban", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/unban", data = "<body>")
 )]
 pub fn unban_user_route(
     db: State<'_, Database>,
     body: Ruma<unban_user::Request>,
-    _room_id: String,
 ) -> ConduitResult<unban_user::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut event = serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
+            .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot unban a user who is not banned.",
@@ -1914,11 +1875,11 @@ pub fn unban_user_route(
 
     db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(), // Sender
+        sender_id.clone(),
         EventType::RoomMember,
         serde_json::to_value(event).expect("event is valid, we just created it"),
         None,
-        Some(body.body.user_id.to_string()),
+        Some(body.user_id.to_string()),
         None,
         &db.globals,
     )?;
@@ -1928,33 +1889,33 @@ pub fn unban_user_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/forget", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/forget", data = "<body>")
 )]
 pub fn forget_room_route(
     db: State<'_, Database>,
     body: Ruma<forget_room::Request>,
-    _room_id: String,
 ) -> ConduitResult<forget_room::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    db.rooms.forget(&body.room_id, &user_id)?;
+    db.rooms.forget(&body.room_id, &sender_id)?;
 
     Ok(forget_room::Response.into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
-    post("/_matrix/client/r0/rooms/<_room_id>/invite", data = "<body>")
+    post("/_matrix/client/r0/rooms/<_>/invite", data = "<body>")
 )]
 pub fn invite_user_route(
     db: State<'_, Database>,
     body: Ruma<invite_user::Request>,
-    _room_id: String,
 ) -> ConduitResult<invite_user::Response> {
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
+
     if let invite_user::InvitationRecipient::UserId { user_id } = &body.recipient {
         db.rooms.append_pdu(
             body.room_id.clone(),
-            body.user_id.clone().expect("user is authenticated"),
+            sender_id.clone(),
             EventType::RoomMember,
             serde_json::to_value(member::MemberEventContent {
                 membership: member::MembershipState::Invite,
@@ -1978,12 +1939,11 @@ pub fn invite_user_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put("/_matrix/client/r0/directory/list/room/<_room_id>", data = "<body>")
+    put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
 )]
 pub async fn set_room_visibility_route(
     db: State<'_, Database>,
     body: Ruma<set_room_visibility::Request>,
-    _room_id: String,
 ) -> ConduitResult<set_room_visibility::Response> {
     match body.visibility {
         room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?,
@@ -1995,12 +1955,11 @@ pub async fn set_room_visibility_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/directory/list/room/<_room_id>", data = "<body>")
+    get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
 )]
 pub async fn get_room_visibility_route(
     db: State<'_, Database>,
     body: Ruma<get_room_visibility::Request>,
-    _room_id: String,
 ) -> ConduitResult<get_room_visibility::Response> {
     Ok(get_room_visibility::Response {
         visibility: if db.rooms.is_public_room(&body.room_id)? {
@@ -2027,7 +1986,7 @@ pub async fn get_public_rooms_route(
                 server,
                 since,
             },
-        user_id,
+        sender_id,
         device_id,
         json_body,
     } = body;
@@ -2047,7 +2006,7 @@ pub async fn get_public_rooms_route(
                 server,
                 since,
             },
-            user_id,
+            sender_id,
             device_id,
             json_body,
         },
@@ -2224,16 +2183,15 @@ pub fn search_users_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/rooms/<_room_id>/members", data = "<body>")
+    get("/_matrix/client/r0/rooms/<_>/members", data = "<body>")
 )]
 pub fn get_member_events_route(
     db: State<'_, Database>,
     body: Ruma<get_member_events::Request>,
-    _room_id: String,
 ) -> ConduitResult<get_member_events::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view this room.",
@@ -2266,19 +2224,17 @@ pub fn get_protocols_route() -> ConduitResult<get_protocols::Response> {
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/rooms/<_room_id>/event/<_event_id>",
+        "/_matrix/client/r0/rooms/<_>/event/<_>",
         data = "<body>"
     )
 )]
 pub fn get_room_event_route(
     db: State<'_, Database>,
     body: Ruma<get_room_event::Request>,
-    _room_id: String,
-    _event_id: String,
 ) -> ConduitResult<get_room_event::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view this room.",
@@ -2298,25 +2254,22 @@ pub fn get_room_event_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/rooms/<_room_id>/send/<_event_type>/<_txn_id>",
+        "/_matrix/client/r0/rooms/<_>/send/<_>/<_>",
         data = "<body>"
     )
 )]
 pub fn create_message_event_route(
     db: State<'_, Database>,
     body: Ruma<create_message_event::Request>,
-    _room_id: String,
-    _event_type: String,
-    _txn_id: String,
 ) -> ConduitResult<create_message_event::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut unsigned = serde_json::Map::new();
     unsigned.insert("transaction_id".to_owned(), body.txn_id.clone().into());
 
     let event_id = db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         body.event_type.clone(),
         serde_json::from_str(
             body.json_body
@@ -2336,18 +2289,15 @@ pub fn create_message_event_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>/<_state_key>",
+        "/_matrix/client/r0/rooms/<_>/state/<_>/<_>",
         data = "<body>"
     )
 )]
 pub fn create_state_event_for_key_route(
     db: State<'_, Database>,
     body: Ruma<create_state_event_for_key::Request>,
-    _room_id: String,
-    _event_type: String,
-    _state_key: String,
 ) -> ConduitResult<create_state_event_for_key::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let content = serde_json::from_str::<serde_json::Value>(
         body.json_body
@@ -2386,7 +2336,7 @@ pub fn create_state_event_for_key_route(
 
     let event_id = db.rooms.append_pdu(
         body.room_id.clone(),
-        user_id.clone(),
+        sender_id.clone(),
         body.event_type.clone(),
         content,
         None,
@@ -2401,15 +2351,13 @@ pub fn create_state_event_for_key_route(
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>",
+        "/_matrix/client/r0/rooms/<_>/state/<_>",
         data = "<body>"
     )
 )]
 pub fn create_state_event_for_empty_key_route(
     db: State<'_, Database>,
     body: Ruma<create_state_event_for_empty_key::Request>,
-    _room_id: String,
-    _event_type: String,
 ) -> ConduitResult<create_state_event_for_empty_key::Response> {
     // This just calls create_state_event_for_key_route
     let Ruma {
@@ -2419,7 +2367,7 @@ pub fn create_state_event_for_empty_key_route(
                 event_type,
                 data,
             },
-        user_id,
+        sender_id,
         device_id,
         json_body,
     } = body;
@@ -2434,13 +2382,10 @@ pub fn create_state_event_for_empty_key_route(
                     data,
                     state_key: "".to_owned(),
                 },
-                user_id,
+                sender_id,
                 device_id,
                 json_body,
             },
-            _room_id,
-            _event_type,
-            "".to_owned(),
         )?
         .0
         .event_id,
@@ -2450,16 +2395,15 @@ pub fn create_state_event_for_empty_key_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/rooms/<_room_id>/state", data = "<body>")
+    get("/_matrix/client/r0/rooms/<_>/state", data = "<body>")
 )]
 pub fn get_state_events_route(
     db: State<'_, Database>,
     body: Ruma<get_state_events::Request>,
-    _room_id: String,
 ) -> ConduitResult<get_state_events::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view the room state.",
@@ -2480,20 +2424,17 @@ pub fn get_state_events_route(
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>/<_state_key>",
+        "/_matrix/client/r0/rooms/<_>/state/<_>/<_>",
         data = "<body>"
     )
 )]
 pub fn get_state_events_for_key_route(
     db: State<'_, Database>,
     body: Ruma<get_state_events_for_key::Request>,
-    _room_id: String,
-    _event_type: String,
-    _state_key: String,
 ) -> ConduitResult<get_state_events_for_key::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view the room state.",
@@ -2518,19 +2459,17 @@ pub fn get_state_events_for_key_route(
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/rooms/<_room_id>/state/<_event_type>",
+        "/_matrix/client/r0/rooms/<_>/state/<_>",
         data = "<body>"
     )
 )]
 pub fn get_state_events_for_empty_key_route(
     db: State<'_, Database>,
     body: Ruma<get_state_events_for_empty_key::Request>,
-    _room_id: String,
-    _event_type: String,
 ) -> ConduitResult<get_state_events_for_empty_key::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view the room state.",
@@ -2556,14 +2495,16 @@ pub fn get_state_events_for_empty_key_route(
     feature = "conduit_bin",
     get("/_matrix/client/r0/sync", data = "<body>")
 )]
-pub fn sync_route(
+pub async fn sync_events_route(
     db: State<'_, Database>,
     body: Ruma<sync_events::Request>,
 ) -> ConduitResult<sync_events::Response> {
-    std::thread::sleep(Duration::from_millis(1000));
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
+    // Setup watchers, so if there's no response, we can wait for them
+    let watcher = db.watch(sender_id, device_id);
+
     let next_batch = db.globals.current_count()?.to_string();
 
     let mut joined_rooms = BTreeMap::new();
@@ -2573,12 +2514,12 @@ pub fn sync_route(
         .and_then(|string| string.parse().ok())
         .unwrap_or(0);
 
-    for room_id in db.rooms.rooms_joined(&user_id) {
+    for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
 
         let mut pdus = db
             .rooms
-            .pdus_since(&user_id, &room_id, since)?
+            .pdus_since(&sender_id, &room_id, since)?
             .filter_map(|r| r.ok()) // Filter out buggy events
             .collect::<Vec<_>>();
 
@@ -2589,7 +2530,7 @@ pub fn sync_route(
             send_notification_counts = true;
             if pdu.kind == EventType::RoomMember {
                 send_member_count = true;
-                if !joined_since_last_sync && pdu.state_key == Some(user_id.to_string()) {
+                if !joined_since_last_sync && pdu.state_key == Some(sender_id.to_string()) {
                     let content = serde_json::from_value::<
                         Raw<ruma::events::room::member::MemberEventContent>,
                     >(pdu.content.clone())
@@ -2621,7 +2562,7 @@ pub fn sync_route(
 
                 for hero in db
                     .rooms
-                    .all_pdus(&user_id, &room_id)?
+                    .all_pdus(&sender_id, &room_id)?
                     .filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
                     .filter(|pdu| pdu.kind == EventType::RoomMember)
                     .map(|pdu| {
@@ -2674,7 +2615,7 @@ pub fn sync_route(
                     // Filter for possible heroes
                     .filter_map(|u| u)
                 {
-                    if heroes.contains(&hero) || hero == user_id.to_string() {
+                    if heroes.contains(&hero) || hero == sender_id.to_string() {
                         continue;
                     }
 
@@ -2692,10 +2633,10 @@ pub fn sync_route(
         };
 
         let notification_count = if send_notification_counts {
-            if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &user_id)? {
+            if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &sender_id)? {
                 Some(
                     (db.rooms
-                        .pdus_since(&user_id, &room_id, last_read)?
+                        .pdus_since(&sender_id, &room_id, last_read)?
                         .filter_map(|pdu| pdu.ok()) // Filter out buggy events
                         .filter(|pdu| {
                             matches!(
@@ -2763,7 +2704,7 @@ pub fn sync_route(
             account_data: sync_events::AccountData {
                 events: db
                     .account_data
-                    .changes_since(Some(&room_id), &user_id, since)?
+                    .changes_since(Some(&room_id), &sender_id, since)?
                     .into_iter()
                     .filter_map(|(_, v)| {
                         serde_json::from_str(v.json().get())
@@ -2807,9 +2748,9 @@ pub fn sync_route(
     }
 
     let mut left_rooms = BTreeMap::new();
-    for room_id in db.rooms.rooms_left(&user_id) {
+    for room_id in db.rooms.rooms_left(&sender_id) {
         let room_id = room_id?;
-        let pdus = db.rooms.pdus_since(&user_id, &room_id, since)?;
+        let pdus = db.rooms.pdus_since(&sender_id, &room_id, since)?;
         let room_events = pdus
             .filter_map(|pdu| pdu.ok()) // Filter out buggy events
             .map(|pdu| pdu.to_sync_room_event())
@@ -2856,7 +2797,7 @@ pub fn sync_route(
     }
 
     let mut invited_rooms = BTreeMap::new();
-    for room_id in db.rooms.rooms_invited(&user_id) {
+    for room_id in db.rooms.rooms_invited(&sender_id) {
         let room_id = room_id?;
 
         let invited_room = sync_events::InvitedRoom {
@@ -2877,9 +2818,9 @@ pub fn sync_route(
 
     // Remove all to-device events the device received *last time*
     db.users
-        .remove_to_device_events(user_id, device_id, since)?;
+        .remove_to_device_events(sender_id, device_id, since)?;
 
-    Ok(sync_events::Response {
+    let response = sync_events::Response {
         next_batch,
         rooms: sync_events::Rooms {
             leave: left_rooms,
@@ -2909,7 +2850,7 @@ pub fn sync_route(
         account_data: sync_events::AccountData {
             events: db
                 .account_data
-                .changes_since(None, &user_id, since)?
+                .changes_since(None, &sender_id, since)?
                 .into_iter()
                 .filter_map(|(_, v)| {
                     serde_json::from_str(v.json().get())
@@ -2931,28 +2872,48 @@ pub fn sync_route(
         },
         device_one_time_keys_count: Default::default(), // TODO
         to_device: sync_events::ToDevice {
-            events: db.users.get_to_device_events(user_id, device_id)?,
+            events: db.users.get_to_device_events(sender_id, device_id)?,
         },
+    };
+
+    // TODO: Retry the endpoint instead of returning (waiting for #118)
+    if !body.full_state && response.rooms.is_empty()
+        && response.presence.is_empty()
+        && response.account_data.is_empty()
+        && response.device_lists.is_empty()
+        && response.device_one_time_keys_count.is_empty()
+        && response.to_device.is_empty()
+    {
+        // Hang a few seconds so requests are not spammed
+        // Stop hanging if new info arrives
+        let mut duration = body.timeout.unwrap_or(Duration::default());
+        if duration.as_secs() > 10 {
+            duration = Duration::from_secs(10);
+        }
+        let mut delay = tokio::time::delay_for(duration);
+        tokio::select! {
+            _ = &mut delay => {}
+            _ = watcher => {}
+        }
     }
-    .into())
+
+    Ok(response.into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/rooms/<_room_id>/context/<_event_id>",
+        "/_matrix/client/r0/rooms/<_>/context/<_>",
         data = "<body>"
     )
 )]
 pub fn get_context_route(
     db: State<'_, Database>,
     body: Ruma<get_context::Request>,
-    _room_id: String,
-    _event_id: String,
 ) -> ConduitResult<get_context::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view this room.",
@@ -2975,7 +2936,7 @@ pub fn get_context_route(
 
     let events_before = db
         .rooms
-        .pdus_until(&user_id, &body.room_id, base_token)
+        .pdus_until(&sender_id, &body.room_id, base_token)
         .take(
             u32::try_from(body.limit).map_err(|_| {
                 Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
@@ -2985,14 +2946,7 @@ pub fn get_context_route(
         .filter_map(|r| r.ok()) // Remove buggy events
         .collect::<Vec<_>>();
 
-    let start_token = events_before.last().map_or(Ok(None), |(_, e)| {
-        Ok::<_, Error>(Some(
-            db.rooms
-                .get_pdu_count(&e.event_id)?
-                .ok_or_else(|| Error::bad_database("Can't find count from event in db."))?
-                .to_string(),
-        ))
-    })?;
+    let start_token = events_before.last().map(|(count, _)| count.to_string());
 
     let events_before = events_before
         .into_iter()
@@ -3001,7 +2955,7 @@ pub fn get_context_route(
 
     let events_after = db
         .rooms
-        .pdus_after(&user_id, &body.room_id, base_token)
+        .pdus_after(&sender_id, &body.room_id, base_token)
         .take(
             u32::try_from(body.limit).map_err(|_| {
                 Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
@@ -3011,15 +2965,7 @@ pub fn get_context_route(
         .filter_map(|r| r.ok()) // Remove buggy events
         .collect::<Vec<_>>();
 
-    let end_token = if let Some(last_event) = events_after.last() {
-        Some(
-            utils::u64_from_bytes(&last_event.0)
-                .map_err(|_| Error::bad_database("Invalid pdu id in db."))?
-                .to_string(),
-        )
-    } else {
-        None
-    };
+    let end_token = events_after.last().map(|(count, _)| count.to_string());
 
     let events_after = events_after
         .into_iter()
@@ -3044,16 +2990,15 @@ pub fn get_context_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get("/_matrix/client/r0/rooms/<_room_id>/messages", data = "<body>")
+    get("/_matrix/client/r0/rooms/<_>/messages", data = "<body>")
 )]
 pub fn get_message_events_route(
     db: State<'_, Database>,
     body: Ruma<get_message_events::Request>,
-    _room_id: String,
 ) -> ConduitResult<get_message_events::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(user_id, &body.room_id)? {
+    if !db.rooms.is_joined(sender_id, &body.room_id)? {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You don't have permission to view this room.",
@@ -3066,7 +3011,7 @@ pub fn get_message_events_route(
         .parse()
         .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from` value."))?;
 
-    let to = body.to.as_ref().map(|t| t.as_bytes());
+    let to = body.to.as_ref().map(|t| t.parse());
 
     // Use limit or else 10
     let limit = body
@@ -3078,21 +3023,13 @@ pub fn get_message_events_route(
         get_message_events::Direction::Forward => {
             let events_after = db
                 .rooms
-                .pdus_after(&user_id, &body.room_id, from)
+                .pdus_after(&sender_id, &body.room_id, from)
                 .take(limit)
                 .filter_map(|r| r.ok()) // Filter out buggy events
-                .take_while(|(k, _)| Some(&**k) != to) // Stop at `to`
+                .take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
                 .collect::<Vec<_>>();
 
-            let end_token = if let Some(last_event) = events_after.last() {
-                Some(
-                    utils::u64_from_bytes(&last_event.0)
-                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?
-                        .to_string(),
-                )
-            } else {
-                None
-            };
+            let end_token = events_after.last().map(|(count, _)| count.to_string());
 
             let events_after = events_after
                 .into_iter()
@@ -3110,21 +3047,13 @@ pub fn get_message_events_route(
         get_message_events::Direction::Backward => {
             let events_before = db
                 .rooms
-                .pdus_until(&user_id, &body.room_id, from)
+                .pdus_until(&sender_id, &body.room_id, from)
                 .take(limit)
                 .filter_map(|r| r.ok()) // Filter out buggy events
-                .take_while(|(k, _)| Some(&**k) != to) // Stop at `to`
+                .take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
                 .collect::<Vec<_>>();
 
-            let start_token = if let Some(last_event) = events_before.last() {
-                Some(
-                    utils::u64_from_bytes(&last_event.0)
-                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?
-                        .to_string(),
-                )
-            } else {
-                None
-            };
+            let start_token = events_before.last().map(|(count, _)| count.to_string());
 
             let events_before = events_before
                 .into_iter()
@@ -3161,24 +3090,22 @@ pub fn publicised_groups_route() -> ConduitResult<create_message_event::Response
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/sendToDevice/<_event_type>/<_txn_id>",
+        "/_matrix/client/r0/sendToDevice/<_>/<_>",
         data = "<body>"
     )
 )]
 pub fn send_event_to_device_route(
     db: State<'_, Database>,
     body: Ruma<send_event_to_device::Request>,
-    _event_type: String,
-    _txn_id: String,
 ) -> ConduitResult<send_event_to_device::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     for (target_user_id, map) in &body.messages {
         for (target_device_id_maybe, event) in map {
             match target_device_id_maybe {
                 to_device::DeviceIdOrAllDevices::DeviceId(target_device_id) => {
                     db.users.add_to_device_event(
-                        user_id,
+                        sender_id,
                         &target_user_id,
                         &target_device_id,
                         &body.event_type,
@@ -3192,7 +3119,7 @@ pub fn send_event_to_device_route(
                 to_device::DeviceIdOrAllDevices::AllDevices => {
                     for target_device_id in db.users.all_device_ids(&target_user_id) {
                         db.users.add_to_device_event(
-                            user_id,
+                            sender_id,
                             &target_user_id,
                             &target_device_id?,
                             &body.event_type,
@@ -3307,11 +3234,11 @@ pub fn get_devices_route(
     db: State<'_, Database>,
     body: Ruma<get_devices::Request>,
 ) -> ConduitResult<get_devices::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let devices = db
         .users
-        .all_devices_metadata(user_id)
+        .all_devices_metadata(sender_id)
         .filter_map(|r| r.ok()) // Filter out buggy devices
         .collect::<Vec<device::Device>>();
 
@@ -3327,11 +3254,11 @@ pub fn get_device_route(
     body: Ruma<get_device::Request>,
     _device_id: String,
 ) -> ConduitResult<get_device::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let device = db
         .users
-        .get_device_metadata(&user_id, &body.body.device_id)?
+        .get_device_metadata(&sender_id, &body.body.device_id)?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
 
     Ok(get_device::Response { device }.into())
@@ -3346,17 +3273,17 @@ pub fn update_device_route(
     body: Ruma<update_device::Request>,
     _device_id: String,
 ) -> ConduitResult<update_device::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut device = db
         .users
-        .get_device_metadata(&user_id, &body.body.device_id)?
+        .get_device_metadata(&sender_id, &body.body.device_id)?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
 
     device.display_name = body.display_name.clone();
 
     db.users
-        .update_device_metadata(&user_id, &body.body.device_id, &device)?;
+        .update_device_metadata(&sender_id, &body.body.device_id, &device)?;
 
     Ok(update_device::Response.into())
 }
@@ -3370,7 +3297,7 @@ pub fn delete_device_route(
     body: Ruma<delete_device::Request>,
     _device_id: String,
 ) -> ConduitResult<delete_device::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     // UIAA
@@ -3386,7 +3313,7 @@ pub fn delete_device_route(
 
     if let Some(auth) = &body.auth {
         let (worked, uiaainfo) = db.uiaa.try_auth(
-            &user_id,
+            &sender_id,
             &device_id,
             auth,
             &uiaainfo,
@@ -3399,11 +3326,11 @@ pub fn delete_device_route(
     // Success!
     } else {
         uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
-        db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
+        db.uiaa.create(&sender_id, &device_id, &uiaainfo)?;
         return Err(Error::Uiaa(uiaainfo));
     }
 
-    db.users.remove_device(&user_id, &body.body.device_id)?;
+    db.users.remove_device(&sender_id, &body.body.device_id)?;
 
     Ok(delete_device::Response.into())
 }
@@ -3416,7 +3343,7 @@ pub fn delete_devices_route(
     db: State<'_, Database>,
     body: Ruma<delete_devices::Request>,
 ) -> ConduitResult<delete_devices::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     // UIAA
@@ -3432,7 +3359,7 @@ pub fn delete_devices_route(
 
     if let Some(auth) = &body.auth {
         let (worked, uiaainfo) = db.uiaa.try_auth(
-            &user_id,
+            &sender_id,
             &device_id,
             auth,
             &uiaainfo,
@@ -3445,12 +3372,12 @@ pub fn delete_devices_route(
     // Success!
     } else {
         uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
-        db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
+        db.uiaa.create(&sender_id, &device_id, &uiaainfo)?;
         return Err(Error::Uiaa(uiaainfo));
     }
 
     for device_id in &body.devices {
-        db.users.remove_device(&user_id, &device_id)?
+        db.users.remove_device(&sender_id, &device_id)?
     }
 
     Ok(delete_devices::Response.into())
@@ -3464,7 +3391,7 @@ pub fn upload_signing_keys_route(
     db: State<'_, Database>,
     body: Ruma<upload_signing_keys::Request>,
 ) -> ConduitResult<upload_signing_keys::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
     // UIAA
@@ -3480,7 +3407,7 @@ pub fn upload_signing_keys_route(
 
     if let Some(auth) = &body.auth {
         let (worked, uiaainfo) = db.uiaa.try_auth(
-            &user_id,
+            &sender_id,
             &device_id,
             auth,
             &uiaainfo,
@@ -3493,13 +3420,13 @@ pub fn upload_signing_keys_route(
     // Success!
     } else {
         uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
-        db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
+        db.uiaa.create(&sender_id, &device_id, &uiaainfo)?;
         return Err(Error::Uiaa(uiaainfo));
     }
 
     if let Some(master_key) = &body.master_key {
         db.users.add_cross_signing_keys(
-            user_id,
+            sender_id,
             &master_key,
             &body.self_signing_key,
             &body.user_signing_key,
@@ -3518,7 +3445,7 @@ pub fn upload_signatures_route(
     db: State<'_, Database>,
     body: Ruma<upload_signatures::Request>,
 ) -> ConduitResult<upload_signatures::Response> {
-    let sender_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     for (user_id, signed_keys) in &body.signed_keys {
         for (key_id, signed_key) in signed_keys {
@@ -3581,22 +3508,19 @@ pub fn set_pushers_route() -> ConduitResult<get_pushers::Response> {
 #[cfg_attr(
     feature = "conduit_bin",
     put(
-        "/_matrix/client/r0/user/<_user_id>/rooms/<_room_id>/tags/<_tag>",
+        "/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>",
         data = "<body>"
     )
 )]
 pub fn update_tag_route(
     db: State<'_, Database>,
-    _user_id: String,
-    _room_id: String,
-    _tag: String,
     body: Ruma<create_tag::Request>,
 ) -> ConduitResult<create_tag::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut tags_event = db
         .account_data
-        .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), user_id, EventType::Tag)?
+        .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), sender_id, EventType::Tag)?
         .unwrap_or_else(|| ruma::events::tag::TagEvent {
             content: ruma::events::tag::TagEventContent {
                 tags: BTreeMap::new(),
@@ -3609,7 +3533,7 @@ pub fn update_tag_route(
 
     db.account_data.update(
         Some(&body.room_id),
-        user_id,
+        sender_id,
         EventType::Tag,
         &tags_event,
         &db.globals,
@@ -3621,22 +3545,19 @@ pub fn update_tag_route(
 #[cfg_attr(
     feature = "conduit_bin",
     delete(
-        "/_matrix/client/r0/user/<_user_id>/rooms/<_room_id>/tags/<_tag>",
+        "/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>",
         data = "<body>"
     )
 )]
 pub fn delete_tag_route(
     db: State<'_, Database>,
-    _user_id: String,
-    _room_id: String,
-    _tag: String,
     body: Ruma<delete_tag::Request>,
 ) -> ConduitResult<delete_tag::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let mut tags_event = db
         .account_data
-        .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), user_id, EventType::Tag)?
+        .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), sender_id, EventType::Tag)?
         .unwrap_or_else(|| ruma::events::tag::TagEvent {
             content: ruma::events::tag::TagEventContent {
                 tags: BTreeMap::new(),
@@ -3646,7 +3567,7 @@ pub fn delete_tag_route(
 
     db.account_data.update(
         Some(&body.room_id),
-        user_id,
+        sender_id,
         EventType::Tag,
         &tags_event,
         &db.globals,
@@ -3658,22 +3579,20 @@ pub fn delete_tag_route(
 #[cfg_attr(
     feature = "conduit_bin",
     get(
-        "/_matrix/client/r0/user/<_user_id>/rooms/<_room_id>/tags",
+        "/_matrix/client/r0/user/<_>/rooms/<_>/tags",
         data = "<body>"
     )
 )]
 pub fn get_tags_route(
     db: State<'_, Database>,
-    _user_id: String,
-    _room_id: String,
     body: Ruma<get_tags::Request>,
 ) -> ConduitResult<get_tags::Response> {
-    let user_id = body.user_id.as_ref().expect("user is authenticated");
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     Ok(get_tags::Response {
         tags: db
             .account_data
-            .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), user_id, EventType::Tag)?
+            .get::<ruma::events::tag::TagEvent>(Some(&body.room_id), sender_id, EventType::Tag)?
             .unwrap_or_else(|| ruma::events::tag::TagEvent {
                 content: ruma::events::tag::TagEventContent {
                     tags: BTreeMap::new(),
@@ -3686,9 +3605,8 @@ pub fn get_tags_route(
 }
 
 #[cfg(feature = "conduit_bin")]
-#[options("/<_segments..>")]
+#[options("/<_..>")]
 pub fn options_route(
-    _segments: rocket::http::uri::Segments<'_>,
 ) -> ConduitResult<send_event_to_device::Response> {
     Ok(send_event_to_device::Response.into())
 }
diff --git a/src/database.rs b/src/database.rs
index 370fde75..250de23c 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -12,7 +12,9 @@ use directories::ProjectDirs;
 use log::info;
 use std::fs::remove_dir_all;
 
-use rocket::Config;
+use futures::StreamExt;
+use rocket::{futures, Config};
+use ruma::{DeviceId, UserId};
 
 pub struct Database {
     pub globals: globals::Globals,
@@ -124,4 +126,77 @@ impl Database {
             _db: db,
         })
     }
+
+    pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> () {
+        let mut userid_prefix = user_id.to_string().as_bytes().to_vec();
+        userid_prefix.push(0xff);
+        let mut userdeviceid_prefix = userid_prefix.clone();
+        userdeviceid_prefix.extend_from_slice(device_id.as_bytes());
+        userdeviceid_prefix.push(0xff);
+
+        let mut futures = futures::stream::FuturesUnordered::new();
+
+        futures.push(self.users.keychangeid_userid.watch_prefix(b""));
+
+        // Return when *any* user changed his key
+        // TODO: only send for user they share a room with
+        futures.push(
+            self.users
+                .todeviceid_events
+                .watch_prefix(&userdeviceid_prefix),
+        );
+
+        // TODO: only send for user they share a room with
+        futures.push(self.global_edus.presenceid_presence.watch_prefix(b""));
+
+        futures.push(self.rooms.userroomid_joined.watch_prefix(&userid_prefix));
+        futures.push(self.rooms.userroomid_invited.watch_prefix(&userid_prefix));
+        futures.push(self.rooms.userroomid_left.watch_prefix(&userid_prefix));
+
+        // Events for rooms we are in
+        for room_id in self.rooms.rooms_joined(user_id).filter_map(|r| r.ok()) {
+            let mut roomid_prefix = room_id.to_string().as_bytes().to_vec();
+            roomid_prefix.push(0xff);
+
+            // PDUs
+            futures.push(self.rooms.pduid_pdu.watch_prefix(&roomid_prefix));
+
+            // EDUs
+            futures.push(
+                self.rooms
+                    .edus
+                    .roomid_lastroomactiveupdate
+                    .watch_prefix(&roomid_prefix),
+            );
+
+            futures.push(
+                self.rooms
+                    .edus
+                    .roomlatestid_roomlatest
+                    .watch_prefix(&roomid_prefix),
+            );
+
+            // Room account data
+            let mut roomuser_prefix = roomid_prefix.clone();
+            roomuser_prefix.extend_from_slice(&userid_prefix);
+
+            futures.push(
+                self.account_data
+                    .roomuserdataid_accountdata
+                    .watch_prefix(&roomuser_prefix),
+            );
+        }
+
+        let mut globaluserdata_prefix = vec![0xff];
+        globaluserdata_prefix.extend_from_slice(&userid_prefix);
+
+        futures.push(
+            self.account_data
+                .roomuserdataid_accountdata
+                .watch_prefix(&globaluserdata_prefix),
+        );
+
+        // Wait until one of them finds something
+        futures.next().await;
+    }
 }
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index 0395cc20..fe5721c7 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -666,7 +666,7 @@ impl Rooms {
         user_id: &UserId,
         room_id: &RoomId,
         until: u64,
-    ) -> impl Iterator<Item = Result<(IVec, PduEvent)>> {
+    ) -> impl Iterator<Item = Result<(u64, PduEvent)>> {
         // Create the first part of the full pdu id
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
@@ -677,6 +677,7 @@ impl Rooms {
         let current: &[u8] = &current;
 
         let user_id = user_id.clone();
+        let prefixlen = prefix.len();
         self.pduid_pdu
             .range(..current)
             .rev()
@@ -688,7 +689,11 @@ impl Rooms {
                 if pdu.sender != user_id {
                     pdu.unsigned.remove("transaction_id");
                 }
-                Ok((k, pdu))
+                Ok((
+                    utils::u64_from_bytes(&k[prefixlen..])
+                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?,
+                    pdu,
+                ))
             })
     }
 
@@ -699,7 +704,7 @@ impl Rooms {
         user_id: &UserId,
         room_id: &RoomId,
         from: u64,
-    ) -> impl Iterator<Item = Result<(IVec, PduEvent)>> {
+    ) -> impl Iterator<Item = Result<(u64, PduEvent)>> {
         // Create the first part of the full pdu id
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
@@ -710,6 +715,7 @@ impl Rooms {
         let current: &[u8] = &current;
 
         let user_id = user_id.clone();
+        let prefixlen = prefix.len();
         self.pduid_pdu
             .range(current..)
             .filter_map(|r| r.ok())
@@ -720,7 +726,11 @@ impl Rooms {
                 if pdu.sender != user_id {
                     pdu.unsigned.remove("transaction_id");
                 }
-                Ok((k, pdu))
+                Ok((
+                    utils::u64_from_bytes(&k[prefixlen..])
+                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?,
+                    pdu,
+                ))
             })
     }
 
@@ -919,7 +929,7 @@ impl Rooms {
             })
     }
 
-    /// Returns an iterator over all left members of a room.
+    /// Returns an iterator over all rooms this user joined.
     pub fn rooms_joined(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
         self.userroomid_joined
             .scan_prefix(user_id.to_string())
diff --git a/src/main.rs b/src/main.rs
index 1feee4d1..2caee4cc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -86,7 +86,7 @@ fn setup_rocket() -> rocket::Rocket {
                 client_server::get_state_events_route,
                 client_server::get_state_events_for_key_route,
                 client_server::get_state_events_for_empty_key_route,
-                client_server::sync_route,
+                client_server::sync_events_route,
                 client_server::get_context_route,
                 client_server::get_message_events_route,
                 client_server::turn_server_route,
diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs
index 2a82b10f..66f4d4c1 100644
--- a/src/ruma_wrapper.rs
+++ b/src/ruma_wrapper.rs
@@ -13,7 +13,7 @@ use {
         http::Status,
         response::{self, Responder},
         tokio::io::AsyncReadExt,
-        Outcome::*,
+        outcome::Outcome::*,
         Request, State,
     },
     ruma::api::Endpoint,
@@ -24,7 +24,7 @@ use {
 /// first.
 pub struct Ruma<T> {
     pub body: T,
-    pub user_id: Option<UserId>,
+    pub sender_id: Option<UserId>,
     pub device_id: Option<Box<DeviceId>>,
     pub json_body: Option<Box<serde_json::value::RawValue>>, // This is None when body is not a valid string
 }
@@ -94,7 +94,7 @@ impl<'a, T: Endpoint> FromTransformedData<'a> for Ruma<T> {
             match T::try_from(http_request) {
                 Ok(t) => Success(Ruma {
                     body: t,
-                    user_id,
+                    sender_id: user_id,
                     device_id,
                     // TODO: Can we avoid parsing it again? (We only need this for append_pdu)
                     json_body: utils::string_from_bytes(&body)

From dd3dab39ae1c2f81aebef1b2041e12046abb61f1 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Mon, 27 Jul 2020 17:38:00 +0200
Subject: [PATCH 2/9] feat: whoami route

---
 src/client_server.rs | 184 +++++++++++++++++++------------------------
 src/main.rs          |   1 +
 src/ruma_wrapper.rs  |   2 +-
 3 files changed, 83 insertions(+), 104 deletions(-)

diff --git a/src/client_server.rs b/src/client_server.rs
index baeb8396..2a67a572 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -11,14 +11,14 @@ use log::warn;
 #[cfg(not(feature = "conduit_bin"))]
 use super::State;
 #[cfg(feature = "conduit_bin")]
-use rocket::{delete, get, options, post, put, State, tokio};
+use rocket::{delete, get, options, post, put, tokio, State};
 
 use ruma::{
     api::client::{
         error::ErrorKind,
         r0::{
             account::{
-                change_password, deactivate, get_username_availability, register,
+                change_password, deactivate, get_username_availability, register, whoami,
                 ThirdPartyIdRemovalStatus,
             },
             alias::{create_alias, delete_alias, get_alias},
@@ -304,6 +304,18 @@ pub fn login_route(
     .into())
 }
 
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/client/r0/account/whoami", data = "<body>")
+)]
+pub fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::Response> {
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
+    Ok(whoami::Response {
+        user_id: sender_id.clone(),
+    }
+    .into())
+}
+
 #[cfg_attr(
     feature = "conduit_bin",
     post("/_matrix/client/r0/logout", data = "<body>")
@@ -361,9 +373,14 @@ pub fn change_password_route(
     };
 
     if let Some(auth) = &body.auth {
-        let (worked, uiaainfo) =
-            db.uiaa
-                .try_auth(&sender_id, device_id, auth, &uiaainfo, &db.users, &db.globals)?;
+        let (worked, uiaainfo) = db.uiaa.try_auth(
+            &sender_id,
+            device_id,
+            auth,
+            &uiaainfo,
+            &db.users,
+            &db.globals,
+        )?;
         if !worked {
             return Err(Error::Uiaa(uiaainfo));
         }
@@ -520,8 +537,7 @@ pub fn get_pushrules_all_route(
     "/_matrix/client/r0/pushrules/<_>/<_>/<_>",
     //data = "<body>"
 ))]
-pub fn set_pushrule_route(
-    //db: State<'_, Database>,
+pub fn set_pushrule_route(//db: State<'_, Database>,
     //body: Ruma<set_pushrule::Request>,
 ) -> ConduitResult<set_pushrule::Response> {
     // TODO
@@ -533,19 +549,14 @@ pub fn set_pushrule_route(
     feature = "conduit_bin",
     put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled")
 )]
-pub fn set_pushrule_enabled_route(
-) -> ConduitResult<set_pushrule_enabled::Response> {
+pub fn set_pushrule_enabled_route() -> ConduitResult<set_pushrule_enabled::Response> {
     // TODO
     warn!("TODO: set_pushrule_enabled_route");
     Ok(set_pushrule_enabled::Response.into())
 }
 
-#[cfg_attr(
-    feature = "conduit_bin",
-    get("/_matrix/client/r0/user/<_>/filter/<_>")
-)]
-pub fn get_filter_route(
-) -> ConduitResult<get_filter::Response> {
+#[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 {
@@ -559,10 +570,7 @@ pub fn get_filter_route(
     .into())
 }
 
-#[cfg_attr(
-    feature = "conduit_bin",
-    post("/_matrix/client/r0/user/<_>/filter")
-)]
+#[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 {
@@ -573,10 +581,7 @@ pub fn create_filter_route() -> ConduitResult<create_filter::Response> {
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/user/<_>/account_data/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
 )]
 pub fn set_global_account_data_route(
     db: State<'_, Database>,
@@ -607,10 +612,7 @@ pub fn set_global_account_data_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/user/<_>/account_data/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
 )]
 pub fn get_global_account_data_route(
     db: State<'_, Database>,
@@ -737,7 +739,8 @@ pub fn set_avatar_url_route(
         // TODO also make sure this is valid mxc:// format (not only starting with it)
     }
 
-    db.users.set_avatar_url(&sender_id, body.avatar_url.clone())?;
+    db.users
+        .set_avatar_url(&sender_id, body.avatar_url.clone())?;
 
     // Send a new membership event into all joined rooms
     for room_id in db.rooms.rooms_joined(&sender_id) {
@@ -1027,10 +1030,7 @@ pub fn create_backup_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/unstable/room_keys/version/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
 )]
 pub fn update_backup_route(
     db: State<'_, Database>,
@@ -1072,23 +1072,20 @@ pub fn get_latest_backup_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/unstable/room_keys/version/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
 )]
 pub fn get_backup_route(
     db: State<'_, Database>,
     body: Ruma<get_backup::Request>,
 ) -> ConduitResult<get_backup::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
-    let algorithm =
-        db.key_backups
-            .get_backup(&sender_id, &body.version)?
-            .ok_or(Error::BadRequest(
-                ErrorKind::NotFound,
-                "Key backup does not exist.",
-            ))?;
+    let algorithm = db
+        .key_backups
+        .get_backup(&sender_id, &body.version)?
+        .ok_or(Error::BadRequest(
+            ErrorKind::NotFound,
+            "Key backup does not exist.",
+        ))?;
 
     Ok(get_backup::Response {
         algorithm,
@@ -1211,10 +1208,7 @@ pub fn set_read_marker_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/rooms/<_>/typing/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/rooms/<_>/typing/<_>", data = "<body>")
 )]
 pub fn create_typing_event_route(
     db: State<'_, Database>,
@@ -1529,10 +1523,7 @@ pub fn joined_rooms_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/rooms/<_>/redact/<_>/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "<body>")
 )]
 pub fn redact_event_route(
     db: State<'_, Database>,
@@ -1695,7 +1686,11 @@ pub fn leave_room_route(
 
     let mut event = serde_json::from_value::<Raw<member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &sender_id.to_string())?
+            .room_state_get(
+                &body.room_id,
+                &EventType::RoomMember,
+                &sender_id.to_string(),
+            )?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot leave a room you are not a member of.",
@@ -1735,7 +1730,11 @@ pub fn kick_user_route(
 
     let mut event = serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
+            .room_state_get(
+                &body.room_id,
+                &EventType::RoomMember,
+                &body.user_id.to_string(),
+            )?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot kick member that's not in the room.",
@@ -1774,7 +1773,11 @@ pub fn joined_members_route(
 ) -> ConduitResult<joined_members::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if !db.rooms.is_joined(&sender_id, &body.room_id).unwrap_or(false) {
+    if !db
+        .rooms
+        .is_joined(&sender_id, &body.room_id)
+        .unwrap_or(false)
+    {
         return Err(Error::BadRequest(
             ErrorKind::Forbidden,
             "You aren't a member of the room.",
@@ -1812,7 +1815,11 @@ pub fn ban_user_route(
 
     let event = db
         .rooms
-        .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
+        .room_state_get(
+            &body.room_id,
+            &EventType::RoomMember,
+            &body.user_id.to_string(),
+        )?
         .map_or(
             Ok::<_, Error>(member::MemberEventContent {
                 membership: member::MembershipState::Ban,
@@ -1859,7 +1866,11 @@ pub fn unban_user_route(
 
     let mut event = serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
         db.rooms
-            .room_state_get(&body.room_id, &EventType::RoomMember, &body.user_id.to_string())?
+            .room_state_get(
+                &body.room_id,
+                &EventType::RoomMember,
+                &body.user_id.to_string(),
+            )?
             .ok_or(Error::BadRequest(
                 ErrorKind::BadState,
                 "Cannot unban a user who is not banned.",
@@ -2223,10 +2234,7 @@ pub fn get_protocols_route() -> ConduitResult<get_protocols::Response> {
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/rooms/<_>/event/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/rooms/<_>/event/<_>", data = "<body>")
 )]
 pub fn get_room_event_route(
     db: State<'_, Database>,
@@ -2253,10 +2261,7 @@ pub fn get_room_event_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/rooms/<_>/send/<_>/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "<body>")
 )]
 pub fn create_message_event_route(
     db: State<'_, Database>,
@@ -2288,10 +2293,7 @@ pub fn create_message_event_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/rooms/<_>/state/<_>/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
 )]
 pub fn create_state_event_for_key_route(
     db: State<'_, Database>,
@@ -2350,10 +2352,7 @@ pub fn create_state_event_for_key_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/rooms/<_>/state/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
 )]
 pub fn create_state_event_for_empty_key_route(
     db: State<'_, Database>,
@@ -2423,10 +2422,7 @@ pub fn get_state_events_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/rooms/<_>/state/<_>/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
 )]
 pub fn get_state_events_for_key_route(
     db: State<'_, Database>,
@@ -2458,10 +2454,7 @@ pub fn get_state_events_for_key_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/rooms/<_>/state/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
 )]
 pub fn get_state_events_for_empty_key_route(
     db: State<'_, Database>,
@@ -2877,7 +2870,8 @@ pub async fn sync_events_route(
     };
 
     // TODO: Retry the endpoint instead of returning (waiting for #118)
-    if !body.full_state && response.rooms.is_empty()
+    if !body.full_state
+        && response.rooms.is_empty()
         && response.presence.is_empty()
         && response.account_data.is_empty()
         && response.device_lists.is_empty()
@@ -2902,10 +2896,7 @@ pub async fn sync_events_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/rooms/<_>/context/<_>",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/rooms/<_>/context/<_>", data = "<body>")
 )]
 pub fn get_context_route(
     db: State<'_, Database>,
@@ -3089,10 +3080,7 @@ pub fn publicised_groups_route() -> ConduitResult<create_message_event::Response
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/sendToDevice/<_>/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/sendToDevice/<_>/<_>", data = "<body>")
 )]
 pub fn send_event_to_device_route(
     db: State<'_, Database>,
@@ -3507,10 +3495,7 @@ pub fn set_pushers_route() -> ConduitResult<get_pushers::Response> {
 
 #[cfg_attr(
     feature = "conduit_bin",
-    put(
-        "/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>",
-        data = "<body>"
-    )
+    put("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
 )]
 pub fn update_tag_route(
     db: State<'_, Database>,
@@ -3544,10 +3529,7 @@ pub fn update_tag_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    delete(
-        "/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>",
-        data = "<body>"
-    )
+    delete("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
 )]
 pub fn delete_tag_route(
     db: State<'_, Database>,
@@ -3578,10 +3560,7 @@ pub fn delete_tag_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/client/r0/user/<_>/rooms/<_>/tags",
-        data = "<body>"
-    )
+    get("/_matrix/client/r0/user/<_>/rooms/<_>/tags", data = "<body>")
 )]
 pub fn get_tags_route(
     db: State<'_, Database>,
@@ -3606,7 +3585,6 @@ pub fn get_tags_route(
 
 #[cfg(feature = "conduit_bin")]
 #[options("/<_..>")]
-pub fn options_route(
-) -> ConduitResult<send_event_to_device::Response> {
+pub fn options_route() -> ConduitResult<send_event_to_device::Response> {
     Ok(send_event_to_device::Response.into())
 }
diff --git a/src/main.rs b/src/main.rs
index 2caee4cc..cc30ff64 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,6 +28,7 @@ fn setup_rocket() -> rocket::Rocket {
                 client_server::register_route,
                 client_server::get_login_route,
                 client_server::login_route,
+                client_server::whoami_route,
                 client_server::logout_route,
                 client_server::logout_all_route,
                 client_server::change_password_route,
diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs
index 66f4d4c1..05934365 100644
--- a/src/ruma_wrapper.rs
+++ b/src/ruma_wrapper.rs
@@ -11,9 +11,9 @@ use {
             Data, FromDataFuture, FromTransformedData, Transform, TransformFuture, Transformed,
         },
         http::Status,
+        outcome::Outcome::*,
         response::{self, Responder},
         tokio::io::AsyncReadExt,
-        outcome::Outcome::*,
         Request, State,
     },
     ruma::api::Endpoint,

From 21eb8d4fe30b86a9b9f9b9e42e9b8df8de384fc6 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Mon, 27 Jul 2020 21:53:28 +0200
Subject: [PATCH 3/9] fix: problems with pdu serialization

---
 Cargo.lock |   3 ++
 Cargo.toml |   2 +-
 src/pdu.rs | 108 +++++++++++++++++++++++++++++++++++++++++------------
 3 files changed, 88 insertions(+), 25 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 37a620b9..e40fd093 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1485,6 +1485,7 @@ dependencies = [
 [[package]]
 name = "rocket"
 version = "0.5.0-dev"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
 dependencies = [
  "async-trait",
  "atomic",
@@ -1509,6 +1510,7 @@ dependencies = [
 [[package]]
 name = "rocket_codegen"
 version = "0.5.0-dev"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
 dependencies = [
  "devise",
  "glob",
@@ -1520,6 +1522,7 @@ dependencies = [
 [[package]]
 name = "rocket_http"
 version = "0.5.0-dev"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
 dependencies = [
  "cookie",
  "http",
diff --git a/Cargo.toml b/Cargo.toml
index e5df8ddb..de8fb47a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ edition = "2018"
 [dependencies]
 # TODO: This can become optional as soon as proper configs are supported
 #rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"] } # Used to handle requests
-rocket = { path = "../rocket/core/lib", features = ["tls"] }
+rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", features = ["tls"] }
 
 tokio = "0.2.22" # Used for long polling
 ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" } # Used for matrix spec type definitions and helpers
diff --git a/src/pdu.rs b/src/pdu.rs
index 0cfdb639..c1492970 100644
--- a/src/pdu.rs
+++ b/src/pdu.rs
@@ -79,39 +79,99 @@ impl PduEvent {
     }
 
     pub fn to_sync_room_event(&self) -> Raw<AnySyncRoomEvent> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<AnySyncRoomEvent>(&json)
-            .map(Raw::from)
-            .expect("AnySyncRoomEvent can always be built from a full PDU event")
+        let mut json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "event_id": self.event_id,
+            "sender": self.sender,
+            "origin_server_ts": self.origin_server_ts,
+            "unsigned": self.unsigned,
+        });
+
+        if let Some(state_key) = &self.state_key {
+            json["state_key"] = json!(state_key);
+        }
+        if let Some(redacts) = &self.redacts {
+            json["redacts"] = json!(redacts);
+        }
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
     pub fn to_room_event(&self) -> Raw<AnyRoomEvent> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<AnyRoomEvent>(&json)
-            .map(Raw::from)
-            .expect("AnyRoomEvent can always be built from a full PDU event")
+        let mut json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "event_id": self.event_id,
+            "sender": self.sender,
+            "origin_server_ts": self.origin_server_ts,
+            "unsigned": self.unsigned,
+            "room_id": self.room_id,
+        });
+
+        if let Some(state_key) = &self.state_key {
+            json["state_key"] = json!(state_key);
+        }
+        if let Some(redacts) = &self.redacts {
+            json["redacts"] = json!(redacts);
+        }
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
     pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<AnyStateEvent>(&json)
-            .map(Raw::from)
-            .expect("AnyStateEvent can always be built from a full PDU event")
+        let json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "event_id": self.event_id,
+            "sender": self.sender,
+            "origin_server_ts": self.origin_server_ts,
+            "unsigned": self.unsigned,
+            "room_id": self.room_id,
+            "state_key": self.state_key,
+        });
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
     pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<AnySyncStateEvent>(&json)
-            .map(Raw::from)
-            .expect("AnySyncStateEvent can always be built from a full PDU event")
+        let json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "event_id": self.event_id,
+            "sender": self.sender,
+            "origin_server_ts": self.origin_server_ts,
+            "unsigned": self.unsigned,
+            "state_key": self.state_key,
+        });
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
     pub fn to_stripped_state_event(&self) -> Raw<AnyStrippedStateEvent> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<AnyStrippedStateEvent>(&json)
-            .map(Raw::from)
-            .expect("AnyStrippedStateEvent can always be built from a full PDU event")
+        let json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "sender": self.sender,
+            "state_key": self.state_key,
+        });
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
     pub fn to_member_event(&self) -> Raw<StateEvent<MemberEventContent>> {
-        let json = serde_json::to_string(&self).expect("PDUs are always valid");
-        serde_json::from_str::<StateEvent<MemberEventContent>>(&json)
-            .map(Raw::from)
-            .expect("StateEvent<MemberEventContent> can always be built from a full PDU event")
+        let json = json!({
+            "content": self.content,
+            "type": self.kind,
+            "event_id": self.event_id,
+            "sender": self.sender,
+            "origin_server_ts": self.origin_server_ts,
+            "redacts": self.redacts,
+            "unsigned": self.unsigned,
+            "room_id": self.room_id,
+            "state_key": self.state_key,
+        });
+
+        serde_json::from_value(json).expect("Raw::from_value always works")
     }
 }

From 05f9d927b833e22709d0eb0b46cc4d15a60f3408 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Tue, 28 Jul 2020 14:02:29 +0200
Subject: [PATCH 4/9] fix: account data

---
 src/client_server.rs         |  7 ++-----
 src/database/account_data.rs | 19 +++++++++++++------
 2 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/src/client_server.rs b/src/client_server.rs
index 2a67a572..6cc8e3d6 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -622,17 +622,14 @@ pub fn get_global_account_data_route(
 
     let data = db
         .account_data
-        .get::<ruma::events::AnyBasicEvent>(
+        .get::<Raw<ruma::events::AnyBasicEvent>>(
             None,
             sender_id,
             EventType::try_from(&body.event_type).expect("EventType::try_from can never fail"),
         )?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
 
-    Ok(get_global_account_data::Response {
-        account_data: Raw::from(data),
-    }
-    .into())
+    Ok(get_global_account_data::Response { account_data: data }.into())
 }
 
 #[cfg_attr(
diff --git a/src/database/account_data.rs b/src/database/account_data.rs
index 1afbcd61..99e0d5c7 100644
--- a/src/database/account_data.rs
+++ b/src/database/account_data.rs
@@ -1,5 +1,6 @@
 use crate::{utils, Error, Result};
 use ruma::{
+    api::client::error::ErrorKind,
     events::{AnyEvent as EduEvent, EventType},
     Raw, RoomId, UserId,
 };
@@ -19,7 +20,7 @@ impl AccountData {
         room_id: Option<&RoomId>,
         user_id: &UserId,
         event_type: EventType,
-        event: &T,
+        data: &T,
         globals: &super::globals::Globals,
     ) -> Result<()> {
         let mut prefix = room_id
@@ -42,10 +43,16 @@ impl AccountData {
         key.push(0xff);
         key.extend_from_slice(event_type.to_string().as_bytes());
 
-        self.roomuserdataid_accountdata.insert(
-            key,
-            &*serde_json::to_string(&event).expect("Map::to_string always works"),
-        )?;
+        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() {
+            return Err(Error::BadRequest(
+                ErrorKind::InvalidParam,
+                "Account data doesn't have all required fields.",
+            ));
+        }
+
+        self.roomuserdataid_accountdata
+            .insert(key, &*json.to_string())?;
 
         Ok(())
     }
@@ -60,7 +67,7 @@ impl AccountData {
         self.find_event(room_id, user_id, &kind)
             .map(|r| {
                 let (_, v) = r?;
-                serde_json::from_slice(&v).map_err(|_| Error::BadDatabase("could not deserialize"))
+                serde_json::from_slice(&v).map_err(|_| Error::bad_database("could not deserialize"))
             })
             .transpose()
     }

From d891bbb5dc0640794feceece27137563e532d9da Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Tue, 28 Jul 2020 15:58:50 +0200
Subject: [PATCH 5/9] improve: presence

---
 Cargo.toml                  |   1 +
 src/client_server.rs        | 189 ++++++++++++++++++++---------------
 src/database.rs             |  10 +-
 src/database/global_edus.rs |  62 ------------
 src/database/rooms/edus.rs  | 191 +++++++++++++++++++++++++++++++++++-
 5 files changed, 304 insertions(+), 149 deletions(-)
 delete mode 100644 src/database/global_edus.rs

diff --git a/Cargo.toml b/Cargo.toml
index de8fb47a..c2607a7d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_p
 
 tokio = "0.2.22" # Used for long polling
 ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "d5d2d1d893fa12d27960e4c58d6c09b215d06e95" } # Used for matrix spec type definitions and helpers
+#ruma = { path = "../ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
 sled = "0.32.0" # Used for storing data permanently
 log = "0.4.8" # Used for emitting log entries
 http = "0.2.1" # Used for rocket<->ruma conversions
diff --git a/src/client_server.rs b/src/client_server.rs
index 6cc8e3d6..de76eef7 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -1,5 +1,5 @@
 use std::{
-    collections::BTreeMap,
+    collections::{hash_map, BTreeMap, HashMap},
     convert::{TryFrom, TryInto},
     time::{Duration, SystemTime},
 };
@@ -645,7 +645,7 @@ pub fn set_displayname_route(
     db.users
         .set_displayname(&sender_id, body.displayname.clone())?;
 
-    // Send a new membership event into all joined rooms
+    // Send a new membership event and presence update into all joined rooms
     for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
         db.rooms.append_pdu(
@@ -675,27 +675,29 @@ pub fn set_displayname_route(
             None,
             &db.globals,
         )?;
-    }
 
-    // Presence update
-    db.global_edus.update_presence(
-        ruma::events::presence::PresenceEvent {
-            content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&sender_id)?,
-                currently_active: None,
-                displayname: db.users.displayname(&sender_id)?,
-                last_active_ago: Some(
-                    utils::millis_since_unix_epoch()
-                        .try_into()
-                        .expect("time is valid"),
-                ),
-                presence: ruma::presence::PresenceState::Online,
-                status_msg: None,
+        // Presence update
+        db.rooms.edus.update_presence(
+            &sender_id,
+            &room_id,
+            ruma::events::presence::PresenceEvent {
+                content: ruma::events::presence::PresenceEventContent {
+                    avatar_url: db.users.avatar_url(&sender_id)?,
+                    currently_active: None,
+                    displayname: db.users.displayname(&sender_id)?,
+                    last_active_ago: Some(
+                        utils::millis_since_unix_epoch()
+                            .try_into()
+                            .expect("time is valid"),
+                    ),
+                    presence: ruma::presence::PresenceState::Online,
+                    status_msg: None,
+                },
+                sender: sender_id.clone(),
             },
-            sender: sender_id.clone(),
-        },
-        &db.globals,
-    )?;
+            &db.globals,
+        )?;
+    }
 
     Ok(set_display_name::Response.into())
 }
@@ -739,7 +741,7 @@ pub fn set_avatar_url_route(
     db.users
         .set_avatar_url(&sender_id, body.avatar_url.clone())?;
 
-    // Send a new membership event into all joined rooms
+    // Send a new membership event and presence update into all joined rooms
     for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
         db.rooms.append_pdu(
@@ -769,27 +771,29 @@ pub fn set_avatar_url_route(
             None,
             &db.globals,
         )?;
-    }
 
-    // Presence update
-    db.global_edus.update_presence(
-        ruma::events::presence::PresenceEvent {
-            content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&sender_id)?,
-                currently_active: None,
-                displayname: db.users.displayname(&sender_id)?,
-                last_active_ago: Some(
-                    utils::millis_since_unix_epoch()
-                        .try_into()
-                        .expect("time is valid"),
-                ),
-                presence: ruma::presence::PresenceState::Online,
-                status_msg: None,
+        // Presence update
+        db.rooms.edus.update_presence(
+            &sender_id,
+            &room_id,
+            ruma::events::presence::PresenceEvent {
+                content: ruma::events::presence::PresenceEventContent {
+                    avatar_url: db.users.avatar_url(&sender_id)?,
+                    currently_active: None,
+                    displayname: db.users.displayname(&sender_id)?,
+                    last_active_ago: Some(
+                        utils::millis_since_unix_epoch()
+                            .try_into()
+                            .expect("time is valid"),
+                    ),
+                    presence: ruma::presence::PresenceState::Online,
+                    status_msg: None,
+                },
+                sender: sender_id.clone(),
             },
-            sender: sender_id.clone(),
-        },
-        &db.globals,
-    )?;
+            &db.globals,
+        )?;
+    }
 
     Ok(set_avatar_url::Response.into())
 }
@@ -844,24 +848,30 @@ pub fn set_presence_route(
 ) -> ConduitResult<set_presence::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    db.global_edus.update_presence(
-        ruma::events::presence::PresenceEvent {
-            content: ruma::events::presence::PresenceEventContent {
-                avatar_url: db.users.avatar_url(&sender_id)?,
-                currently_active: None,
-                displayname: db.users.displayname(&sender_id)?,
-                last_active_ago: Some(
-                    utils::millis_since_unix_epoch()
-                        .try_into()
-                        .expect("time is valid"),
-                ),
-                presence: body.presence,
-                status_msg: body.status_msg.clone(),
+    for room_id in db.rooms.rooms_joined(&sender_id) {
+        let room_id = room_id?;
+
+        db.rooms.edus.update_presence(
+            &sender_id,
+            &room_id,
+            ruma::events::presence::PresenceEvent {
+                content: ruma::events::presence::PresenceEventContent {
+                    avatar_url: db.users.avatar_url(&sender_id)?,
+                    currently_active: None,
+                    displayname: db.users.displayname(&sender_id)?,
+                    last_active_ago: Some(
+                        utils::millis_since_unix_epoch()
+                            .try_into()
+                            .expect("time is valid"),
+                    ),
+                    presence: body.presence,
+                    status_msg: body.status_msg.clone(),
+                },
+                sender: sender_id.clone(),
             },
-            sender: sender_id.clone(),
-        },
-        &db.globals,
-    )?;
+            &db.globals,
+        )?;
+    }
 
     Ok(set_presence::Response.into())
 }
@@ -2492,6 +2502,9 @@ pub async fn sync_events_route(
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
 
+    // TODO: match body.set_presence {
+    db.rooms.edus.ping_presence(&sender_id)?;
+
     // Setup watchers, so if there's no response, we can wait for them
     let watcher = db.watch(sender_id, device_id);
 
@@ -2504,6 +2517,8 @@ pub async fn sync_events_route(
         .and_then(|string| string.parse().ok())
         .unwrap_or(0);
 
+    let mut presence_updates = HashMap::new();
+
     for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
 
@@ -2735,6 +2750,40 @@ pub async fn sync_events_route(
         if !joined_room.is_empty() {
             joined_rooms.insert(room_id.clone(), joined_room);
         }
+
+        // Take presence updates from this room
+        for (user_id, presence) in
+            db.rooms
+                .edus
+                .presence_since(&room_id, since, &db.rooms, &db.globals)?
+        {
+            match presence_updates.entry(user_id) {
+                hash_map::Entry::Vacant(v) => {
+                    v.insert(presence);
+                }
+                hash_map::Entry::Occupied(mut o) => {
+                    let p = o.get_mut();
+
+                    // Update existing presence event with more info
+                    p.content.presence = presence.content.presence;
+                    if let Some(status_msg) = presence.content.status_msg {
+                        p.content.status_msg = Some(status_msg);
+                    }
+                    if let Some(last_active_ago) = presence.content.last_active_ago {
+                        p.content.last_active_ago = Some(last_active_ago);
+                    }
+                    if let Some(displayname) = presence.content.displayname {
+                        p.content.displayname = Some(displayname);
+                    }
+                    if let Some(avatar_url) = presence.content.avatar_url {
+                        p.content.avatar_url = Some(avatar_url);
+                    }
+                    if let Some(currently_active) = presence.content.currently_active {
+                        p.content.currently_active = Some(currently_active);
+                    }
+                }
+            }
+        }
     }
 
     let mut left_rooms = BTreeMap::new();
@@ -2818,23 +2867,9 @@ pub async fn sync_events_route(
             invite: invited_rooms,
         },
         presence: sync_events::Presence {
-            events: db
-                .global_edus
-                .presence_since(since)?
-                .map(|edu| {
-                    let mut edu = edu?
-                        .deserialize()
-                        .map_err(|_| Error::bad_database("EDU in database is invalid."))?;
-                    if let Some(timestamp) = edu.content.last_active_ago {
-                        let mut last_active_ago = utils::millis_since_unix_epoch()
-                            .try_into()
-                            .expect("time is valid");
-                        last_active_ago -= timestamp;
-                        edu.content.last_active_ago = Some(last_active_ago);
-                    }
-                    Ok::<_, Error>(edu.into())
-                })
-                .filter_map(|edu| edu.ok()) // Filter out buggy events
+            events: presence_updates
+                .into_iter()
+                .map(|(_, v)| Raw::from(v))
                 .collect(),
         },
         account_data: sync_events::AccountData {
@@ -2878,8 +2913,8 @@ pub async fn sync_events_route(
         // Hang a few seconds so requests are not spammed
         // Stop hanging if new info arrives
         let mut duration = body.timeout.unwrap_or(Duration::default());
-        if duration.as_secs() > 10 {
-            duration = Duration::from_secs(10);
+        if duration.as_secs() > 30 {
+            duration = Duration::from_secs(30);
         }
         let mut delay = tokio::time::delay_for(duration);
         tokio::select! {
diff --git a/src/database.rs b/src/database.rs
index 250de23c..a8376388 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -1,5 +1,4 @@
 pub(self) mod account_data;
-pub(self) mod global_edus;
 pub(self) mod globals;
 pub(self) mod key_backups;
 pub(self) mod media;
@@ -22,7 +21,6 @@ pub struct Database {
     pub uiaa: uiaa::Uiaa,
     pub rooms: rooms::Rooms,
     pub account_data: account_data::AccountData,
-    pub global_edus: global_edus::GlobalEdus,
     pub media: media::Media,
     pub key_backups: key_backups::KeyBackups,
     pub _db: sled::Db,
@@ -93,6 +91,8 @@ impl Database {
                     roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest")?, // Read receipts
                     roomactiveid_userid: db.open_tree("roomactiveid_userid")?, // Typing notifs
                     roomid_lastroomactiveupdate: db.open_tree("roomid_lastroomactiveupdate")?,
+                    presenceid_presence: db.open_tree("presenceid_presence")?,
+                    userid_lastpresenceupdate: db.open_tree("userid_lastpresenceupdate")?,
                 },
                 pduid_pdu: db.open_tree("pduid_pdu")?,
                 eventid_pduid: db.open_tree("eventid_pduid")?,
@@ -112,9 +112,6 @@ impl Database {
             account_data: account_data::AccountData {
                 roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
             },
-            global_edus: global_edus::GlobalEdus {
-                presenceid_presence: db.open_tree("presenceid_presence")?, // Presence
-            },
             media: media::Media {
                 mediaid_file: db.open_tree("mediaid_file")?,
             },
@@ -146,9 +143,6 @@ impl Database {
                 .watch_prefix(&userdeviceid_prefix),
         );
 
-        // TODO: only send for user they share a room with
-        futures.push(self.global_edus.presenceid_presence.watch_prefix(b""));
-
         futures.push(self.rooms.userroomid_joined.watch_prefix(&userid_prefix));
         futures.push(self.rooms.userroomid_invited.watch_prefix(&userid_prefix));
         futures.push(self.rooms.userroomid_left.watch_prefix(&userid_prefix));
diff --git a/src/database/global_edus.rs b/src/database/global_edus.rs
deleted file mode 100644
index 94f2de82..00000000
--- a/src/database/global_edus.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use crate::{Error, Result};
-use ruma::Raw;
-
-pub struct GlobalEdus {
-    //pub globalallid_globalall: sled::Tree, // ToDevice, GlobalAllId = UserId + Count
-    pub(super) presenceid_presence: sled::Tree, // Presence, PresenceId = Count + UserId
-}
-
-impl GlobalEdus {
-    /// Adds a global event which will be saved until a new event replaces it (e.g. presence updates).
-    pub fn update_presence(
-        &self,
-        presence: ruma::events::presence::PresenceEvent,
-        globals: &super::globals::Globals,
-    ) -> Result<()> {
-        // Remove old entry
-        if let Some(old) = self
-            .presenceid_presence
-            .iter()
-            .keys()
-            .rev()
-            .filter_map(|r| r.ok())
-            .find(|key| {
-                key.rsplit(|&b| b == 0xff)
-                    .next()
-                    .expect("rsplit always returns an element")
-                    == presence.sender.to_string().as_bytes()
-            })
-        {
-            // This is the old global_latest
-            self.presenceid_presence.remove(old)?;
-        }
-
-        let mut presence_id = globals.next_count()?.to_be_bytes().to_vec();
-        presence_id.push(0xff);
-        presence_id.extend_from_slice(&presence.sender.to_string().as_bytes());
-
-        self.presenceid_presence.insert(
-            presence_id,
-            &*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"),
-        )?;
-
-        Ok(())
-    }
-
-    /// Returns an iterator over the most recent presence updates that happened after the event with id `since`.
-    pub fn presence_since(
-        &self,
-        since: u64,
-    ) -> Result<impl Iterator<Item = Result<Raw<ruma::events::presence::PresenceEvent>>>> {
-        let first_possible_edu = (since + 1).to_be_bytes().to_vec(); // +1 so we don't send the event at since
-
-        Ok(self
-            .presenceid_presence
-            .range(&*first_possible_edu..)
-            .filter_map(|r| r.ok())
-            .map(|(_, v)| {
-                Ok(serde_json::from_slice(&v)
-                    .map_err(|_| Error::bad_database("Invalid presence event in db."))?)
-            }))
-    }
-}
diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs
index 22d01666..62df0ccd 100644
--- a/src/database/rooms/edus.rs
+++ b/src/database/rooms/edus.rs
@@ -1,15 +1,25 @@
 use crate::{utils, Error, Result};
+use js_int::UInt;
 use ruma::{
-    events::{AnyEvent as EduEvent, SyncEphemeralRoomEvent},
+    events::{
+        presence::{PresenceEvent, PresenceEventContent},
+        AnyEvent as EduEvent, SyncEphemeralRoomEvent,
+    },
+    presence::PresenceState,
     Raw, RoomId, UserId,
 };
-use std::convert::TryFrom;
+use std::{
+    collections::HashMap,
+    convert::{TryFrom, TryInto},
+};
 
 pub struct RoomEdus {
     pub(in super::super) roomuserid_lastread: sled::Tree, // RoomUserId = Room + User
     pub(in super::super) roomlatestid_roomlatest: sled::Tree, // Read Receipts, RoomLatestId = RoomId + Count + UserId
     pub(in super::super) roomactiveid_userid: sled::Tree, // Typing, RoomActiveId = RoomId + TimeoutTime + Count
     pub(in super::super) roomid_lastroomactiveupdate: sled::Tree, // LastRoomActiveUpdate = Count
+    pub(in super::super) presenceid_presence: sled::Tree, // PresenceId = RoomId + Count + UserId
+    pub(in super::super) userid_lastpresenceupdate: sled::Tree, // LastPresenceUpdate = Count
 }
 
 impl RoomEdus {
@@ -263,4 +273,181 @@ impl RoomEdus {
             })?))
         })
     }
+
+    /// Adds a presence event which will be saved until a new event replaces it.
+    ///
+    /// Note: This method takes a RoomId because presence updates are always bound to rooms to
+    /// make sure users outside these rooms can't see them.
+    pub fn update_presence(
+        &self,
+        user_id: &UserId,
+        room_id: &RoomId,
+        presence: ruma::events::presence::PresenceEvent,
+        globals: &super::super::globals::Globals,
+    ) -> Result<()> {
+        // TODO: Remove old entry? Or maybe just wipe completely from time to time?
+
+        let count = globals.next_count()?.to_be_bytes();
+
+        let mut presence_id = room_id.to_string().as_bytes().to_vec();
+        presence_id.push(0xff);
+        presence_id.extend_from_slice(&count);
+        presence_id.push(0xff);
+        presence_id.extend_from_slice(&presence.sender.to_string().as_bytes());
+
+        self.presenceid_presence.insert(
+            presence_id,
+            &*serde_json::to_string(&presence).expect("PresenceEvent can be serialized"),
+        )?;
+
+        self.userid_lastpresenceupdate.insert(
+            &user_id.to_string().as_bytes(),
+            &utils::millis_since_unix_epoch().to_be_bytes(),
+        )?;
+
+        Ok(())
+    }
+
+    /// Resets the presence timeout, so the user will stay in their current presence state.
+    pub fn ping_presence(&self, user_id: &UserId) -> Result<()> {
+        self.userid_lastpresenceupdate.insert(
+            &user_id.to_string().as_bytes(),
+            &utils::millis_since_unix_epoch().to_be_bytes(),
+        )?;
+
+        Ok(())
+    }
+
+    /// 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>> {
+        self.userid_lastpresenceupdate
+            .get(&user_id.to_string().as_bytes())?
+            .map(|bytes| {
+                utils::u64_from_bytes(&bytes).map_err(|_| {
+                    Error::bad_database("Invalid timestamp in userid_lastpresenceupdate.")
+                })
+            })
+            .transpose()
+    }
+
+    /// Sets all users to offline who have been quiet for too long.
+    pub fn presence_maintain(
+        &self,
+        rooms: &super::Rooms,
+        globals: &super::super::globals::Globals,
+    ) -> Result<()> {
+        let current_timestamp = utils::millis_since_unix_epoch();
+
+        for (user_id_bytes, last_timestamp) in self
+            .userid_lastpresenceupdate
+            .iter()
+            .filter_map(|r| r.ok())
+            .filter_map(|(k, bytes)| {
+                Some((
+                    k,
+                    utils::u64_from_bytes(&bytes)
+                        .map_err(|_| {
+                            Error::bad_database("Invalid timestamp in userid_lastpresenceupdate.")
+                        })
+                        .ok()?,
+                ))
+            })
+            .take_while(|(_, timestamp)| current_timestamp - timestamp > 5 * 60_000) // 5 Minutes
+        {
+            self.userid_lastpresenceupdate.remove(&user_id_bytes)?;
+
+            // Send new presence events to set the user offline
+            let count = globals.next_count()?.to_be_bytes();
+            let user_id = utils::string_from_bytes(&user_id_bytes)
+                .map_err(|_| {
+                    Error::bad_database("Invalid UserId bytes in userid_lastpresenceupdate.")
+                })?
+                .try_into()
+                .map_err(|_| Error::bad_database("Invalid UserId in userid_lastpresenceupdate."))?;
+            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();
+                presence_id.push(0xff);
+                presence_id.extend_from_slice(&count);
+                presence_id.push(0xff);
+                presence_id.extend_from_slice(&user_id_bytes);
+
+                self.presenceid_presence.insert(
+                    presence_id,
+                    &*serde_json::to_string(&PresenceEvent {
+                        content: PresenceEventContent {
+                            avatar_url: None,
+                            currently_active: None,
+                            displayname: None,
+                            last_active_ago: Some(
+                                last_timestamp.try_into().expect("time is valid"),
+                            ),
+                            presence: PresenceState::Offline,
+                            status_msg: None,
+                        },
+                        sender: user_id.clone(),
+                    })
+                    .expect("PresenceEvent can be serialized"),
+                )?;
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Returns an iterator over the most recent presence updates that happened after the event with id `since`.
+    pub fn presence_since(
+        &self,
+        room_id: &RoomId,
+        since: u64,
+        rooms: &super::Rooms,
+        globals: &super::super::globals::Globals,
+    ) -> Result<HashMap<UserId, PresenceEvent>> {
+        self.presence_maintain(rooms, globals)?;
+
+        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        prefix.push(0xff);
+
+        let mut first_possible_edu = prefix.clone();
+        first_possible_edu.extend_from_slice(&(since + 1).to_be_bytes()); // +1 so we don't send the event at since
+        let mut hashmap = HashMap::new();
+
+        for (key, value) in self
+            .presenceid_presence
+            .range(&*first_possible_edu..)
+            .filter_map(|r| r.ok())
+            .take_while(|(key, _)| key.starts_with(&prefix))
+        {
+            let user_id = UserId::try_from(
+                utils::string_from_bytes(
+                    key.rsplit(|&b| b == 0xff)
+                        .next()
+                        .expect("rsplit always returns an element"),
+                )
+                .map_err(|_| Error::bad_database("Invalid UserId bytes in presenceid_presence."))?,
+            )
+            .map_err(|_| Error::bad_database("Invalid UserId in presenceid_presence."))?;
+
+            let mut presence = serde_json::from_slice::<PresenceEvent>(&value)
+                .map_err(|_| Error::bad_database("Invalid presence event in db."))?;
+
+            let current_timestamp: UInt = utils::millis_since_unix_epoch()
+                .try_into()
+                .expect("time is valid");
+
+            if presence.content.presence == PresenceState::Online {
+                // Don't set last_active_ago when the user is online
+                presence.content.last_active_ago = None;
+            } else {
+                // Convert from timestamp to duration
+                presence.content.last_active_ago = presence
+                    .content
+                    .last_active_ago
+                    .map(|timestamp| current_timestamp - timestamp);
+            }
+
+            hashmap.insert(user_id, presence);
+        }
+
+        Ok(hashmap)
+    }
 }

From 310b0fcd869449e0206e85817a4703a6d882ced2 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Tue, 28 Jul 2020 22:21:57 +0200
Subject: [PATCH 6/9] fix ci

---
 sytest/sytest-whitelist | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/sytest/sytest-whitelist b/sytest/sytest-whitelist
index ec2aba4e..3c1095b3 100644
--- a/sytest/sytest-whitelist
+++ b/sytest/sytest-whitelist
@@ -36,7 +36,6 @@ Current state appears in timeline in private history
 Current state appears in timeline in private history with many messages before
 Deleted tags appear in an incremental v2 /sync
 Deleting a non-existent alias should return a 404
-Device messages over federation wake up /sync
 Device messages wake up /sync
 Events come down the correct room
 GET /device/{deviceId}
@@ -120,6 +119,6 @@ User in shared private room does appear in user directory
 User is offline if they set_presence=offline in their sync
 Users with sufficient power-level can delete other's aliases
 Version responds 200 OK with valid structure
-Wildcard device messages over federation wake up /sync
+We should see our own leave event when rejecting an invite, even if history_visibility is restricted (riot-web/3462)
 Wildcard device messages wake up /sync
 query for user with no keys returns empty key dict

From 069338776919c88f6f90fbd0a6be3fa6b26c2de2 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Wed, 29 Jul 2020 17:03:04 +0200
Subject: [PATCH 7/9] improvement: more efficient /sync and only send device
 updates when sharing a room

---
 src/client_server.rs  | 80 +++++++++++++++++++++++++++----------------
 src/database/rooms.rs | 37 ++++++--------------
 src/database/users.rs | 57 +++++++++++++++++++++++-------
 3 files changed, 105 insertions(+), 69 deletions(-)

diff --git a/src/client_server.rs b/src/client_server.rs
index de76eef7..cd61746b 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -1,5 +1,5 @@
 use std::{
-    collections::{hash_map, BTreeMap, HashMap},
+    collections::{hash_map, BTreeMap, HashMap, HashSet},
     convert::{TryFrom, TryInto},
     time::{Duration, SystemTime},
 };
@@ -898,7 +898,7 @@ pub fn upload_keys_route(
         // This check is needed to assure that signatures are kept
         if db.users.get_device_keys(sender_id, device_id)?.is_none() {
             db.users
-                .add_device_keys(sender_id, device_id, device_keys, &db.globals)?;
+                .add_device_keys(sender_id, device_id, device_keys, &db.rooms, &db.globals)?;
         }
     }
 
@@ -2518,20 +2518,41 @@ pub async fn sync_events_route(
         .unwrap_or(0);
 
     let mut presence_updates = HashMap::new();
+    let mut device_list_updates = HashSet::new();
 
     for room_id in db.rooms.rooms_joined(&sender_id) {
         let room_id = room_id?;
 
-        let mut pdus = db
+        let mut non_timeline_pdus = db
             .rooms
             .pdus_since(&sender_id, &room_id, since)?
-            .filter_map(|r| r.ok()) // Filter out buggy events
+            .filter_map(|r| r.ok()); // Filter out buggy events
+
+        // Take the last 10 events for the timeline
+        let timeline_pdus = non_timeline_pdus
+            .by_ref()
+            .rev()
+            .take(10)
+            .collect::<Vec<_>>()
+            .into_iter()
+            .rev()
             .collect::<Vec<_>>();
 
+        // They /sync response doesn't always return all messages, so we say the output is
+        // limited unless there are events in non_timeline_pdus
+        //let mut limited = false;
+
+        let mut state_pdus = Vec::new();
+        for pdu in non_timeline_pdus {
+            if pdu.state_key.is_some() {
+                state_pdus.push(pdu);
+            }
+        }
+
         let mut send_member_count = false;
         let mut joined_since_last_sync = false;
         let mut send_notification_counts = false;
-        for pdu in &pdus {
+        for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)?.filter_map(|r| r.ok()) {
             send_notification_counts = true;
             if pdu.kind == EventType::RoomMember {
                 send_member_count = true;
@@ -2544,8 +2565,8 @@ pub async fn sync_events_route(
                     .map_err(|_| Error::bad_database("Invalid PDU in database."))?;
                     if content.membership == ruma::events::room::member::MembershipState::Join {
                         joined_since_last_sync = true;
-                        // Both send_member_count and joined_since_last_sync are set. There's nothing more
-                        // to do
+                        // Both send_member_count and joined_since_last_sync are set. There's
+                        // nothing more to do
                         break;
                     }
                 }
@@ -2574,7 +2595,7 @@ pub async fn sync_events_route(
                         let content = serde_json::from_value::<
                             Raw<ruma::events::room::member::MemberEventContent>,
                         >(pdu.content.clone())
-                        .map_err(|_| Error::bad_database("Invalid member event in database."))?
+                        .expect("Raw::from_value always works")
                         .deserialize()
                         .map_err(|_| Error::bad_database("Invalid member event in database."))?;
 
@@ -2592,7 +2613,7 @@ pub async fn sync_events_route(
                                     .content
                                     .clone(),
                             )
-                            .map_err(|_| Error::bad_database("Invalid member event in database."))?
+                            .expect("Raw::from_value always works")
                             .deserialize()
                             .map_err(|_| {
                                 Error::bad_database("Invalid member event in database.")
@@ -2659,15 +2680,7 @@ pub async fn sync_events_route(
             None
         };
 
-        // They /sync response doesn't always return all messages, so we say the output is
-        // limited unless there are enough events
-        let mut limited = true;
-        pdus = pdus.split_off(pdus.len().checked_sub(10).unwrap_or_else(|| {
-            limited = false;
-            0
-        }));
-
-        let prev_batch = pdus.first().map_or(Ok::<_, Error>(None), |e| {
+        let prev_batch = timeline_pdus.first().map_or(Ok::<_, Error>(None), |e| {
             Ok(Some(
                 db.rooms
                     .get_pdu_count(&e.event_id)?
@@ -2676,7 +2689,7 @@ pub async fn sync_events_route(
             ))
         })?;
 
-        let room_events = pdus
+        let room_events = timeline_pdus
             .into_iter()
             .map(|pdu| pdu.to_sync_room_event())
             .collect::<Vec<_>>();
@@ -2728,7 +2741,7 @@ pub async fn sync_events_route(
                 notification_count,
             },
             timeline: sync_events::Timeline {
-                limited: limited || joined_since_last_sync,
+                limited: false || joined_since_last_sync,
                 prev_batch,
                 events: room_events,
             },
@@ -2751,6 +2764,13 @@ pub async fn sync_events_route(
             joined_rooms.insert(room_id.clone(), joined_room);
         }
 
+        // Look for device list updates in this room
+        device_list_updates.extend(
+            db.users
+                .keys_changed(&room_id, since)
+                .filter_map(|r| r.ok()),
+        );
+
         // Take presence updates from this room
         for (user_id, presence) in
             db.rooms
@@ -2885,14 +2905,7 @@ pub async fn sync_events_route(
                 .collect::<Vec<_>>(),
         },
         device_lists: sync_events::DeviceLists {
-            changed: if since != 0 {
-                db.users
-                    .keys_changed(since)
-                    .filter_map(|u| u.ok())
-                    .collect() // Filter out buggy events
-            } else {
-                Vec::new()
-            },
+            changed: device_list_updates.into_iter().collect(),
             left: Vec::new(), // TODO
         },
         device_one_time_keys_count: Default::default(), // TODO
@@ -3450,6 +3463,7 @@ pub fn upload_signing_keys_route(
             &master_key,
             &body.self_signing_key,
             &body.user_signing_key,
+            &db.rooms,
             &db.globals,
         )?;
     }
@@ -3500,8 +3514,14 @@ pub fn upload_signatures_route(
                         ))?
                         .to_owned(),
                 );
-                db.users
-                    .sign_key(&user_id, &key_id, signature, &sender_id, &db.globals)?;
+                db.users.sign_key(
+                    &user_id,
+                    &key_id,
+                    signature,
+                    &sender_id,
+                    &db.rooms,
+                    &db.globals,
+                )?;
             }
         }
     }
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index fe5721c7..4cd47a17 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -611,44 +611,29 @@ impl Rooms {
         self.pdus_since(user_id, room_id, 0)
     }
 
-    /// Returns an iterator over all events in a room that happened after the event with id `since`.
+    /// Returns an iterator over all events in a room that happened after the event with id `since`
+    /// in reverse-chronological order.
     pub fn pdus_since(
         &self,
         user_id: &UserId,
         room_id: &RoomId,
         since: u64,
-    ) -> Result<impl Iterator<Item = Result<PduEvent>>> {
-        // Create the first part of the full pdu id
-        let mut pdu_id = room_id.to_string().as_bytes().to_vec();
-        pdu_id.push(0xff);
-        pdu_id.extend_from_slice(&(since).to_be_bytes());
-
-        self.pdus_since_pduid(user_id, room_id, &pdu_id)
-    }
-
-    /// Returns an iterator over all events in a room that happened after the event with id `since`.
-    pub fn pdus_since_pduid(
-        &self,
-        user_id: &UserId,
-        room_id: &RoomId,
-        pdu_id: &[u8],
-    ) -> Result<impl Iterator<Item = Result<PduEvent>>> {
-        // Create the first part of the full pdu id
+    ) -> Result<impl DoubleEndedIterator<Item = Result<PduEvent>>> {
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
 
+        // Skip the first pdu if it's exactly at since, because we sent that last time
+        let mut first_pdu_id = prefix.clone();
+        first_pdu_id.extend_from_slice(&(since+1).to_be_bytes());
+
+        let mut last_pdu_id = prefix.clone();
+        last_pdu_id.extend_from_slice(&u64::MAX.to_be_bytes());
+
         let user_id = user_id.clone();
         Ok(self
             .pduid_pdu
-            .range(pdu_id..)
-            // Skip the first pdu if it's exactly at since, because we sent that last time
-            .skip(if self.pduid_pdu.get(pdu_id)?.is_some() {
-                1
-            } else {
-                0
-            })
+            .range(first_pdu_id..last_pdu_id)
             .filter_map(|r| r.ok())
-            .take_while(move |(k, _)| k.starts_with(&prefix))
             .map(move |(_, v)| {
                 let mut pdu = serde_json::from_slice::<PduEvent>(&v)
                     .map_err(|_| Error::bad_database("PDU in db is invalid."))?;
diff --git a/src/database/users.rs b/src/database/users.rs
index 5030f32e..7fbdd806 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -9,7 +9,7 @@ use ruma::{
         },
     },
     events::{AnyToDeviceEvent, EventType},
-    DeviceId, Raw, UserId,
+    DeviceId, Raw, UserId, RoomId,
 };
 use std::{collections::BTreeMap, convert::TryFrom, mem, time::SystemTime};
 
@@ -22,7 +22,7 @@ pub struct Users {
     pub(super) token_userdeviceid: sled::Tree,
 
     pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
-    pub(super) keychangeid_userid: sled::Tree,       // KeyChangeId = Count
+    pub(super) keychangeid_userid: sled::Tree,       // KeyChangeId = RoomId + Count
     pub(super) keyid_key: sled::Tree,                // KeyId = UserId + KeyId (depends on key type)
     pub(super) userid_masterkeyid: sled::Tree,
     pub(super) userid_selfsigningkeyid: sled::Tree,
@@ -371,6 +371,7 @@ impl Users {
         user_id: &UserId,
         device_id: &DeviceId,
         device_keys: &DeviceKeys,
+        rooms: &super::rooms::Rooms,
         globals: &super::globals::Globals,
     ) -> Result<()> {
         let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
@@ -382,8 +383,15 @@ impl Users {
             &*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"),
         )?;
 
-        self.keychangeid_userid
-            .insert(globals.next_count()?.to_be_bytes(), &*user_id.to_string())?;
+        let count = globals.next_count()?.to_be_bytes();
+        for room_id in rooms.rooms_joined(&user_id) {
+            let mut key = room_id?.to_string().as_bytes().to_vec();
+            key.push(0xff);
+            key.extend_from_slice(&count);
+
+            self.keychangeid_userid
+                .insert(key, &*user_id.to_string())?;
+        }
 
         Ok(())
     }
@@ -394,6 +402,7 @@ impl Users {
         master_key: &CrossSigningKey,
         self_signing_key: &Option<CrossSigningKey>,
         user_signing_key: &Option<CrossSigningKey>,
+        rooms: &super::rooms::Rooms,
         globals: &super::globals::Globals,
     ) -> Result<()> {
         // TODO: Check signatures
@@ -482,8 +491,15 @@ impl Users {
                 .insert(&*user_id.to_string(), user_signing_key_key)?;
         }
 
-        self.keychangeid_userid
-            .insert(globals.next_count()?.to_be_bytes(), &*user_id.to_string())?;
+        let count = globals.next_count()?.to_be_bytes();
+        for room_id in rooms.rooms_joined(&user_id) {
+            let mut key = room_id?.to_string().as_bytes().to_vec();
+            key.push(0xff);
+            key.extend_from_slice(&count);
+
+            self.keychangeid_userid
+                .insert(key, &*user_id.to_string())?;
+        }
 
         Ok(())
     }
@@ -494,6 +510,7 @@ impl Users {
         key_id: &str,
         signature: (String, String),
         sender_id: &UserId,
+        rooms: &super::rooms::Rooms,
         globals: &super::globals::Globals,
     ) -> Result<()> {
         let mut key = target_id.to_string().as_bytes().to_vec();
@@ -525,19 +542,33 @@ impl Users {
                 .expect("CrossSigningKey::to_string always works"),
         )?;
 
-        self.keychangeid_userid
-            .insert(globals.next_count()?.to_be_bytes(), &*target_id.to_string())?;
+        // TODO: Should we notify about this change?
+        let count = globals.next_count()?.to_be_bytes();
+        for room_id in rooms.rooms_joined(&target_id) {
+            let mut key = room_id?.to_string().as_bytes().to_vec();
+            key.push(0xff);
+            key.extend_from_slice(&count);
+
+            self.keychangeid_userid
+                .insert(key, &*target_id.to_string())?;
+        }
 
         Ok(())
     }
 
-    pub fn keys_changed(&self, since: u64) -> impl Iterator<Item = Result<UserId>> {
+    pub fn keys_changed(&self, room_id: &RoomId, since: u64) -> impl Iterator<Item = Result<UserId>> {
+        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        prefix.push(0xff);
+        let mut start = prefix.clone();
+        start.extend_from_slice(&(since + 1).to_be_bytes());
+
         self.keychangeid_userid
-            .range((since + 1).to_be_bytes()..)
-            .values()
-            .map(|bytes| {
+            .range(start..)
+            .filter_map(|r| r.ok())
+            .take_while(move |(k, _)| k.starts_with(&prefix))
+            .map(|(_, bytes)| {
                 Ok(
-                    UserId::try_from(utils::string_from_bytes(&bytes?).map_err(|_| {
+                    UserId::try_from(utils::string_from_bytes(&bytes).map_err(|_| {
                         Error::bad_database(
                             "User ID in devicekeychangeid_userid is invalid unicode.",
                         )

From 66bc25fcd36940bb32c1873c0beed3335604bd55 Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Wed, 29 Jul 2020 17:37:26 +0200
Subject: [PATCH 8/9] feat: implement /keys/changes

---
 Cargo.lock                 | 70 +++++++++++++++++++-------------------
 src/client_server.rs       | 43 +++++++++++++++++++++--
 src/database/rooms.rs      |  6 ++--
 src/database/rooms/edus.rs |  3 +-
 src/database/users.rs      | 23 ++++++++-----
 src/main.rs                |  1 +
 6 files changed, 96 insertions(+), 50 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e40fd093..225dc09a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -214,9 +214,9 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
 
 [[package]]
 name = "bytemuck"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d40636046a60a45ee5185e885a3ccb771f7a2065fb7cbcc2a7ecfd9896d1c365"
+checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
 
 [[package]]
 name = "byteorder"
@@ -814,9 +814,9 @@ dependencies = [
 
 [[package]]
 name = "image"
-version = "0.23.7"
+version = "0.23.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2397fc43bd5648b7117aabb3c5e62d0e62c194826ec77b0b4d0c41e62744635"
+checksum = "543904170510c1b5fb65140485d84de4a57fddb2ed685481e9020ce3d2c9f64c"
 dependencies = [
  "bytemuck",
  "byteorder",
@@ -882,9 +882,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.42"
+version = "0.3.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52732a3d3ad72c58ad2dc70624f9c17b46ecd0943b9a4f1ee37c4c18c5d983e2"
+checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -916,9 +916,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.73"
+version = "0.2.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9"
+checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
 
 [[package]]
 name = "lock_api"
@@ -1239,18 +1239,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
 
 [[package]]
 name = "pin-project"
-version = "0.4.22"
+version = "0.4.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17"
+checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "0.4.22"
+version = "0.4.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7"
+checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1305,9 +1305,9 @@ checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
 
 [[package]]
 name = "proc-macro-hack"
-version = "0.5.16"
+version = "0.5.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
+checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598"
 
 [[package]]
 name = "proc-macro-nested"
@@ -1485,7 +1485,7 @@ dependencies = [
 [[package]]
 name = "rocket"
 version = "0.5.0-dev"
-source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f6d40ecd5d871d97837b3116eb670fb3c06d95b9"
 dependencies = [
  "async-trait",
  "atomic",
@@ -1510,7 +1510,7 @@ dependencies = [
 [[package]]
 name = "rocket_codegen"
 version = "0.5.0-dev"
-source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f6d40ecd5d871d97837b3116eb670fb3c06d95b9"
 dependencies = [
  "devise",
  "glob",
@@ -1522,7 +1522,7 @@ dependencies = [
 [[package]]
 name = "rocket_http"
 version = "0.5.0-dev"
-source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f383f15047d0dda71dd21399dfea96161fe2bd0e"
+source = "git+https://github.com/timokoesters/Rocket.git?branch=empty_parameters#f6d40ecd5d871d97837b3116eb670fb3c06d95b9"
 dependencies = [
  "cookie",
  "http",
@@ -1838,9 +1838,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.56"
+version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3"
+checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
 dependencies = [
  "itoa",
  "ryu",
@@ -2029,9 +2029,9 @@ checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1"
 
 [[package]]
 name = "syn"
-version = "1.0.35"
+version = "1.0.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0"
+checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2339,9 +2339,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.65"
+version = "0.2.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d"
+checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
 dependencies = [
  "cfg-if",
  "serde",
@@ -2351,9 +2351,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.65"
+version = "0.2.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ed2fb8c84bfad20ea66b26a3743f3e7ba8735a69fe7d95118c33ec8fc1244d"
+checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -2366,9 +2366,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.15"
+version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41ad6e4e8b2b7f8c90b6e09a9b590ea15cb0d1dbe28502b5a405cd95d1981671"
+checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -2378,9 +2378,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.65"
+version = "0.2.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb071268b031a64d92fc6cf691715ca5a40950694d8f683c5bb43db7c730929e"
+checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2388,9 +2388,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.65"
+version = "0.2.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf592c807080719d1ff2f245a687cbadb3ed28b2077ed7084b47aba8b691f2c6"
+checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2401,15 +2401,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.65"
+version = "0.2.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b6c0220ded549d63860c78c38f3bcc558d1ca3f4efa74942c536ddbbb55e87"
+checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
 
 [[package]]
 name = "web-sys"
-version = "0.3.42"
+version = "0.3.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8be2398f326b7ba09815d0b403095f34dd708579220d099caae89be0b32137b2"
+checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
diff --git a/src/client_server.rs b/src/client_server.rs
index cd61746b..0536c8ea 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -35,7 +35,7 @@ use ruma::{
                 set_room_visibility,
             },
             filter::{self, create_filter, get_filter},
-            keys::{self, claim_keys, get_keys, upload_keys},
+            keys::{self, claim_keys, get_key_changes, get_keys, upload_keys},
             media::{create_content, get_content, get_content_thumbnail, get_media_config},
             membership::{
                 ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
@@ -2552,7 +2552,11 @@ pub async fn sync_events_route(
         let mut send_member_count = false;
         let mut joined_since_last_sync = false;
         let mut send_notification_counts = false;
-        for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)?.filter_map(|r| r.ok()) {
+        for pdu in db
+            .rooms
+            .pdus_since(&sender_id, &room_id, since)?
+            .filter_map(|r| r.ok())
+        {
             send_notification_counts = true;
             if pdu.kind == EventType::RoomMember {
                 send_member_count = true;
@@ -2767,7 +2771,7 @@ pub async fn sync_events_route(
         // Look for device list updates in this room
         device_list_updates.extend(
             db.users
-                .keys_changed(&room_id, since)
+                .keys_changed(&room_id, since, None)
                 .filter_map(|r| r.ok()),
         );
 
@@ -3529,6 +3533,39 @@ pub fn upload_signatures_route(
     Ok(upload_signatures::Response.into())
 }
 
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/client/r0/keys/changes", data = "<body>")
+)]
+pub fn get_key_changes_route(
+    db: State<'_, Database>,
+    body: Ruma<get_key_changes::Request>,
+) -> ConduitResult<get_key_changes::Response> {
+    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
+
+    let mut device_list_updates = HashSet::new();
+    for room_id in db.rooms.rooms_joined(sender_id).filter_map(|r| r.ok()) {
+        device_list_updates.extend(
+            db.users
+                .keys_changed(
+                    &room_id,
+                    body.from.parse().map_err(|_| {
+                        Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")
+                    })?,
+                    Some(body.to.parse().map_err(|_| {
+                        Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")
+                    })?),
+                )
+                .filter_map(|r| r.ok()),
+        );
+    }
+    Ok(get_key_changes::Response {
+        changed: device_list_updates.into_iter().collect(),
+        left: Vec::new(), // TODO
+    }
+    .into())
+}
+
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/pushers"))]
 pub fn pushers_route() -> ConduitResult<get_pushers::Response> {
     Ok(get_pushers::Response {
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index 4cd47a17..7895f025 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -611,8 +611,8 @@ impl Rooms {
         self.pdus_since(user_id, room_id, 0)
     }
 
-    /// Returns an iterator over all events in a room that happened after the event with id `since`
-    /// in reverse-chronological order.
+    /// Returns a double-ended iterator over all events in a room that happened after the event with id `since`
+    /// in chronological order.
     pub fn pdus_since(
         &self,
         user_id: &UserId,
@@ -624,7 +624,7 @@ impl Rooms {
 
         // Skip the first pdu if it's exactly at since, because we sent that last time
         let mut first_pdu_id = prefix.clone();
-        first_pdu_id.extend_from_slice(&(since+1).to_be_bytes());
+        first_pdu_id.extend_from_slice(&(since + 1).to_be_bytes());
 
         let mut last_pdu_id = prefix.clone();
         last_pdu_id.extend_from_slice(&u64::MAX.to_be_bytes());
diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs
index 62df0ccd..fff30c22 100644
--- a/src/database/rooms/edus.rs
+++ b/src/database/rooms/edus.rs
@@ -352,7 +352,8 @@ impl RoomEdus {
                         .ok()?,
                 ))
             })
-            .take_while(|(_, timestamp)| current_timestamp - timestamp > 5 * 60_000) // 5 Minutes
+            .take_while(|(_, timestamp)| current_timestamp - timestamp > 5 * 60_000)
+        // 5 Minutes
         {
             self.userid_lastpresenceupdate.remove(&user_id_bytes)?;
 
diff --git a/src/database/users.rs b/src/database/users.rs
index 7fbdd806..7fb97679 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -9,7 +9,7 @@ use ruma::{
         },
     },
     events::{AnyToDeviceEvent, EventType},
-    DeviceId, Raw, UserId, RoomId,
+    DeviceId, Raw, RoomId, UserId,
 };
 use std::{collections::BTreeMap, convert::TryFrom, mem, time::SystemTime};
 
@@ -389,8 +389,7 @@ impl Users {
             key.push(0xff);
             key.extend_from_slice(&count);
 
-            self.keychangeid_userid
-                .insert(key, &*user_id.to_string())?;
+            self.keychangeid_userid.insert(key, &*user_id.to_string())?;
         }
 
         Ok(())
@@ -497,8 +496,7 @@ impl Users {
             key.push(0xff);
             key.extend_from_slice(&count);
 
-            self.keychangeid_userid
-                .insert(key, &*user_id.to_string())?;
+            self.keychangeid_userid.insert(key, &*user_id.to_string())?;
         }
 
         Ok(())
@@ -556,14 +554,23 @@ impl Users {
         Ok(())
     }
 
-    pub fn keys_changed(&self, room_id: &RoomId, since: u64) -> impl Iterator<Item = Result<UserId>> {
+    pub fn keys_changed(
+        &self,
+        room_id: &RoomId,
+        from: u64,
+        to: Option<u64>,
+    ) -> impl Iterator<Item = Result<UserId>> {
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
+
         let mut start = prefix.clone();
-        start.extend_from_slice(&(since + 1).to_be_bytes());
+        start.extend_from_slice(&(from + 1).to_be_bytes());
+
+        let mut end = prefix.clone();
+        end.extend_from_slice(&to.unwrap_or(u64::MAX).to_be_bytes());
 
         self.keychangeid_userid
-            .range(start..)
+            .range(start..end)
             .filter_map(|r| r.ok())
             .take_while(move |(k, _)| k.starts_with(&prefix))
             .map(|(_, bytes)| {
diff --git a/src/main.rs b/src/main.rs
index cc30ff64..86d8446b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -108,6 +108,7 @@ fn setup_rocket() -> rocket::Rocket {
                 client_server::options_route,
                 client_server::upload_signing_keys_route,
                 client_server::upload_signatures_route,
+                client_server::get_key_changes_route,
                 client_server::pushers_route,
                 client_server::set_pushers_route,
                 //server_server::well_known_server,

From e0d0fb4703de4fd245ad193bc8b109ccad1939da Mon Sep 17 00:00:00 2001
From: timokoesters <timo@koesters.xyz>
Date: Wed, 29 Jul 2020 20:44:06 +0200
Subject: [PATCH 9/9] fix: only send device_one_time_keys_count when there are
 updates

---
 src/client_server.rs  | 10 +++++++---
 src/database.rs       |  1 +
 src/database/users.rs | 25 +++++++++++++++++++++++++
 3 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/src/client_server.rs b/src/client_server.rs
index 0536c8ea..4a2f33b3 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -890,7 +890,7 @@ pub fn upload_keys_route(
     if let Some(one_time_keys) = &body.one_time_keys {
         for (key_key, key_value) in one_time_keys {
             db.users
-                .add_one_time_key(sender_id, device_id, key_key, key_value)?;
+                .add_one_time_key(sender_id, device_id, key_key, key_value, &db.globals)?;
         }
     }
 
@@ -1002,7 +1002,7 @@ pub fn claim_keys_route(
         for (device_id, key_algorithm) in map {
             if let Some(one_time_keys) =
                 db.users
-                    .take_one_time_key(user_id, device_id, key_algorithm)?
+                    .take_one_time_key(user_id, device_id, key_algorithm, &db.globals)?
             {
                 let mut c = BTreeMap::new();
                 c.insert(one_time_keys.0, one_time_keys.1);
@@ -2912,7 +2912,11 @@ pub async fn sync_events_route(
             changed: device_list_updates.into_iter().collect(),
             left: Vec::new(), // TODO
         },
-        device_one_time_keys_count: Default::default(), // TODO
+        device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_id)? > since {
+            db.users.count_one_time_keys(sender_id, device_id)?
+        } else {
+            BTreeMap::new()
+        },
         to_device: sync_events::ToDevice {
             events: db.users.get_to_device_events(sender_id, device_id)?,
         },
diff --git a/src/database.rs b/src/database.rs
index a8376388..5a1ed0f6 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -75,6 +75,7 @@ impl Database {
                 userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?,
                 token_userdeviceid: db.open_tree("token_userdeviceid")?,
                 onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?,
+                userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?,
                 keychangeid_userid: db.open_tree("devicekeychangeid_userid")?,
                 keyid_key: db.open_tree("keyid_key")?,
                 userid_masterkeyid: db.open_tree("userid_masterkeyid")?,
diff --git a/src/database/users.rs b/src/database/users.rs
index 7fb97679..c7927677 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -22,6 +22,7 @@ pub struct Users {
     pub(super) token_userdeviceid: sled::Tree,
 
     pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
+    pub(super) userid_lastonetimekeyupdate: sled::Tree, // LastOneTimeKeyUpdate = Count
     pub(super) keychangeid_userid: sled::Tree,       // KeyChangeId = RoomId + Count
     pub(super) keyid_key: sled::Tree,                // KeyId = UserId + KeyId (depends on key type)
     pub(super) userid_masterkeyid: sled::Tree,
@@ -270,6 +271,7 @@ impl Users {
         device_id: &DeviceId,
         one_time_key_key: &AlgorithmAndDeviceId,
         one_time_key_value: &OneTimeKey,
+        globals: &super::globals::Globals,
     ) -> Result<()> {
         let mut key = user_id.to_string().as_bytes().to_vec();
         key.push(0xff);
@@ -294,14 +296,32 @@ impl Users {
                 .expect("OneTimeKey::to_string always works"),
         )?;
 
+        self.userid_lastonetimekeyupdate.insert(
+            &user_id.to_string().as_bytes(),
+            &globals.next_count()?.to_be_bytes(),
+        )?;
+
         Ok(())
     }
 
+    pub fn last_one_time_keys_update(&self, user_id: &UserId) -> Result<u64> {
+        self
+            .userid_lastonetimekeyupdate
+            .get(&user_id.to_string().as_bytes())?
+            .map(|bytes| {
+                utils::u64_from_bytes(&bytes).map_err(|_| {
+                    Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.")
+                })
+            })
+            .unwrap_or(Ok(0))
+    }
+
     pub fn take_one_time_key(
         &self,
         user_id: &UserId,
         device_id: &DeviceId,
         key_algorithm: &KeyAlgorithm,
+        globals: &super::globals::Globals,
     ) -> Result<Option<(AlgorithmAndDeviceId, OneTimeKey)>> {
         let mut prefix = user_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
@@ -311,6 +331,11 @@ impl Users {
         prefix.extend_from_slice(key_algorithm.to_string().as_bytes());
         prefix.push(b':');
 
+        self.userid_lastonetimekeyupdate.insert(
+            &user_id.to_string().as_bytes(),
+            &globals.next_count()?.to_be_bytes(),
+        )?;
+
         self.onetimekeyid_onetimekeys
             .scan_prefix(&prefix)
             .next()