diff --git a/Cargo.lock b/Cargo.lock
index 98dbac9c56f3a36f6d2e782ea3e4042c21b99c29..a658ee2b8481350b2667cb8091766d131451f171 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,57 +22,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
 [[package]]
-name = "aead"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "aes"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7001367fde4c768a19d1029f0a8be5abd9308e1119846d5bd9ad26297b8faf5"
-dependencies = [
- "aes-soft",
- "aesni",
- "block-cipher",
-]
-
-[[package]]
-name = "aes-gcm"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f5007801316299f922a6198d1d09a0bae95786815d066d5880d13f7c45ead1"
-dependencies = [
- "aead",
- "aes",
- "block-cipher",
- "ghash",
- "subtle",
-]
-
-[[package]]
-name = "aes-soft"
-version = "0.4.0"
+name = "ansi_term"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 dependencies = [
- "block-cipher",
- "byteorder",
- "opaque-debug 0.2.3",
-]
-
-[[package]]
-name = "aesni"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264"
-dependencies = [
- "block-cipher",
- "opaque-debug 0.2.3",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -81,12 +36,6 @@ version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
 
-[[package]]
-name = "array-init"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f30bbe2f5e3d117f55bd8c7a1f9191e4a5deba9f15f595bbea4f670c59c765db"
-
 [[package]]
 name = "arrayref"
 version = "0.3.6"
@@ -107,9 +56,9 @@ checksum = "4af5687fe33aec5e70ef14caac5e0d363e335e5e5d6385fb75978d0c241b1d67"
 
 [[package]]
 name = "async-trait"
-version = "0.1.38"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1a4a2f97ce50c9d0282c1468816208588441492b40d813b2e0419c22c05e7f"
+checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -141,14 +90,14 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
 name = "backtrace"
-version = "0.3.50"
+version = "0.3.53"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
+checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
 dependencies = [
  "addr2line",
- "cfg-if",
+ "cfg-if 1.0.0",
  "libc",
- "miniz_oxide 0.4.1",
+ "miniz_oxide 0.4.3",
  "object",
  "rustc-demangle",
 ]
@@ -188,24 +137,6 @@ dependencies = [
  "constant_time_eq",
 ]
 
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "block-cipher"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "bumpalo"
 version = "3.4.0"
@@ -214,9 +145,9 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
 
 [[package]]
 name = "bytemuck"
-version = "1.3.1"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
+checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
 
 [[package]]
 name = "byteorder"
@@ -232,9 +163,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
 
 [[package]]
 name = "cc"
-version = "1.0.59"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381"
+checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
 
 [[package]]
 name = "cfg-if"
@@ -242,6 +173,25 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time 0.1.44",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "cloudabi"
 version = "0.1.0"
@@ -253,9 +203,9 @@ dependencies = [
 
 [[package]]
 name = "color_quant"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
 [[package]]
 name = "conduit"
@@ -269,16 +219,25 @@ dependencies = [
  "log",
  "rand",
  "reqwest",
+ "ring",
  "rocket",
  "ruma",
  "rust-argon2",
  "serde",
  "serde_json",
  "sled",
+ "state-res",
  "thiserror",
  "tokio",
+ "trust-dns-resolver",
 ]
 
+[[package]]
+name = "const_fn"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2"
+
 [[package]]
 name = "constant_time_eq"
 version = "0.1.5"
@@ -291,13 +250,8 @@ version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0"
 dependencies = [
- "aes-gcm",
- "base64",
- "hkdf",
  "percent-encoding",
- "rand",
- "sha2",
- "time 0.2.16",
+ "time 0.2.22",
  "version_check",
 ]
 
@@ -317,19 +271,13 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
 
-[[package]]
-name = "cpuid-bool"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
-
 [[package]]
 name = "crc32fast"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
 ]
 
 [[package]]
@@ -339,7 +287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
 dependencies = [
  "autocfg",
- "cfg-if",
+ "cfg-if 0.1.10",
  "crossbeam-utils",
  "lazy_static",
  "maybe-uninit",
@@ -354,20 +302,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
 dependencies = [
  "autocfg",
- "cfg-if",
+ "cfg-if 0.1.10",
  "lazy_static",
 ]
 
-[[package]]
-name = "crypto-mac"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
 [[package]]
 name = "deflate"
 version = "0.8.6"
@@ -408,22 +346,12 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "directories"
-version = "2.0.2"
+version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
+checksum = "f8fed639d60b58d0f53498ab13d26f621fd77569cc6edb031f4cc36a2ad9da0f"
 dependencies = [
- "cfg-if",
  "dirs-sys",
 ]
 
@@ -450,13 +378,31 @@ version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
 
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -518,9 +464,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 
 [[package]]
 name = "futures"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
+checksum = "5d8e3078b7b2a8a671cb7a3d17b4760e4181ea243227776ba83fd043b4ca034e"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -533,9 +479,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
+checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -543,15 +489,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
+checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
+checksum = "cc709ca1da6f66143b8c9bec8e6260181869893714e9b5a490b169b0414144ab"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -560,15 +506,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
+checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
+checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b"
 dependencies = [
  "proc-macro-hack",
  "proc-macro2",
@@ -578,24 +524,24 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
+checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd"
 
 [[package]]
 name = "futures-task"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
+checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94"
 dependencies = [
  "once_cell",
 ]
 
 [[package]]
 name = "futures-util"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
+checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -620,44 +566,25 @@ dependencies = [
  "byteorder",
 ]
 
-[[package]]
-name = "generic-array"
-version = "0.14.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
-dependencies = [
- "typenum",
- "version_check",
-]
-
 [[package]]
 name = "getrandom"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
 
-[[package]]
-name = "ghash"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6e27f0689a6e15944bdce7e45425efb87eaa8ab0c6e87f11d0987a9133e2531"
-dependencies = [
- "polyval",
-]
-
 [[package]]
 name = "gif"
-version = "0.10.3"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af"
+checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4"
 dependencies = [
  "color_quant",
- "lzw",
+ "weezl",
 ]
 
 [[package]]
@@ -693,12 +620,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.8.2"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
-dependencies = [
- "autocfg",
-]
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
 
 [[package]]
 name = "heck"
@@ -711,31 +635,22 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.15"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
 dependencies = [
  "libc",
 ]
 
 [[package]]
-name = "hkdf"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe1149865383e4526a43aee8495f9a325f0b806c63ce6427d06336a590abbbc9"
-dependencies = [
- "digest",
- "hmac",
-]
-
-[[package]]
-name = "hmac"
-version = "0.8.1"
+name = "hostname"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
 dependencies = [
- "crypto-mac",
- "digest",
+ "libc",
+ "match_cfg",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -765,11 +680,17 @@ version = "1.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
 
+[[package]]
+name = "httpdate"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
+
 [[package]]
 name = "hyper"
-version = "0.13.7"
+version = "0.13.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e68a8dd9716185d9e64ea473ea6ef63529252e3e27623295a0378a19665d5eb"
+checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -779,10 +700,10 @@ dependencies = [
  "http",
  "http-body",
  "httparse",
+ "httpdate",
  "itoa",
  "pin-project",
  "socket2",
- "time 0.1.44",
  "tokio",
  "tower-service",
  "tracing",
@@ -815,9 +736,9 @@ dependencies = [
 
 [[package]]
 name = "image"
-version = "0.23.8"
+version = "0.23.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "543904170510c1b5fb65140485d84de4a57fddb2ed685481e9020ce3d2c9f64c"
+checksum = "985fc06b1304d19c28d5c562ed78ef5316183f2b0053b46763a0b94862373c34"
 dependencies = [
  "bytemuck",
  "byteorder",
@@ -831,9 +752,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.5.1"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
 dependencies = [
  "autocfg",
  "hashbrown",
@@ -847,9 +768,12 @@ checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
 
 [[package]]
 name = "instant"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485"
+checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
+dependencies = [
+ "cfg-if 0.1.10",
+]
 
 [[package]]
 name = "iovec"
@@ -860,12 +784,33 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "ipconfig"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
+dependencies = [
+ "socket2",
+ "widestring",
+ "winapi 0.3.9",
+ "winreg 0.6.2",
+]
+
 [[package]]
 name = "ipnet"
 version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
 
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.6"
@@ -883,9 +828,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.44"
+version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73"
+checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -917,9 +862,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.76"
+version = "0.2.79"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
+checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
 
 [[package]]
 name = "lock_api"
@@ -936,14 +887,38 @@ version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
 ]
 
 [[package]]
-name = "lzw"
-version = "0.10.0"
+name = "lru-cache"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
 
 [[package]]
 name = "matches"
@@ -965,9 +940,9 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
 
 [[package]]
 name = "memoffset"
-version = "0.5.5"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
 dependencies = [
  "autocfg",
 ]
@@ -999,11 +974,12 @@ dependencies = [
 
 [[package]]
 name = "miniz_oxide"
-version = "0.4.1"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722"
+checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
 dependencies = [
  "adler",
+ "autocfg",
 ]
 
 [[package]]
@@ -1012,7 +988,7 @@ version = "0.6.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "fuchsia-zircon",
  "fuchsia-zircon-sys",
  "iovec",
@@ -1068,11 +1044,11 @@ dependencies = [
 
 [[package]]
 name = "net2"
-version = "0.2.34"
+version = "0.2.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
+checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "libc",
  "winapi 0.3.9",
 ]
@@ -1130,9 +1106,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.20.0"
+version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
+checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
 
 [[package]]
 name = "once_cell"
@@ -1140,18 +1116,6 @@ version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
 
-[[package]]
-name = "opaque-debug"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
-
-[[package]]
-name = "opaque-debug"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
-
 [[package]]
 name = "openssl"
 version = "0.10.30"
@@ -1159,7 +1123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
 dependencies = [
  "bitflags",
- "cfg-if",
+ "cfg-if 0.1.10",
  "foreign-types",
  "lazy_static",
  "libc",
@@ -1202,7 +1166,7 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "cloudabi",
  "instant",
  "libc",
@@ -1240,18 +1204,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
 
 [[package]]
 name = "pin-project"
-version = "0.4.23"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa"
+checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "0.4.23"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f"
+checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1260,9 +1224,9 @@ dependencies = [
 
 [[package]]
 name = "pin-project-lite"
-version = "0.1.7"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715"
+checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
 
 [[package]]
 name = "pin-utils"
@@ -1272,9 +1236,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
 name = "pkg-config"
-version = "0.3.18"
+version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
 
 [[package]]
 name = "png"
@@ -1288,16 +1252,6 @@ dependencies = [
  "miniz_oxide 0.3.7",
 ]
 
-[[package]]
-name = "polyval"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9a50142b55ab3ed0e9f68dfb3709f1d90d29da24e91033f28b96330643107dc"
-dependencies = [
- "cfg-if",
- "universal-hash",
-]
-
 [[package]]
 name = "ppv-lite86"
 version = "0.2.9"
@@ -1327,9 +1281,9 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.19"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
 dependencies = [
  "unicode-xid",
 ]
@@ -1346,6 +1300,12 @@ dependencies = [
  "yansi",
 ]
 
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
 [[package]]
 name = "quote"
 version = "1.0.7"
@@ -1433,6 +1393,31 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "regex"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
+dependencies = [
+ "byteorder",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
+
 [[package]]
 name = "remove_dir_all"
 version = "0.5.3"
@@ -1474,7 +1459,17 @@ dependencies = [
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "winreg",
+ "winreg 0.7.0",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
+dependencies = [
+ "hostname",
+ "quick-error",
 ]
 
 [[package]]
@@ -1510,7 +1505,7 @@ dependencies = [
  "rocket_codegen",
  "rocket_http",
  "state",
- "time 0.2.16",
+ "time 0.2.22",
  "tokio",
  "toml",
  "version_check",
@@ -1545,7 +1540,7 @@ dependencies = [
  "ref-cast",
  "smallvec",
  "state",
- "time 0.2.16",
+ "time 0.2.22",
  "tokio",
  "tokio-rustls",
  "unicode-xid",
@@ -1554,21 +1549,23 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.0.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "ruma-api",
+ "ruma-appservice-api",
  "ruma-client-api",
  "ruma-common",
  "ruma-events",
  "ruma-federation-api",
  "ruma-identifiers",
+ "ruma-serde",
  "ruma-signatures",
 ]
 
 [[package]]
 name = "ruma-api"
 version = "0.17.0-alpha.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "http",
  "percent-encoding",
@@ -1583,7 +1580,7 @@ dependencies = [
 [[package]]
 name = "ruma-api-macros"
 version = "0.17.0-alpha.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -1591,14 +1588,28 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "ruma-appservice-api"
+version = "0.2.0-alpha.1"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
+dependencies = [
+ "ruma-api",
+ "ruma-common",
+ "ruma-events",
+ "ruma-identifiers",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "ruma-client-api"
 version = "0.10.0-alpha.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "assign",
  "http",
  "js_int",
+ "percent-encoding",
  "ruma-api",
  "ruma-common",
  "ruma-events",
@@ -1612,9 +1623,10 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.2.0"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "js_int",
+ "ruma-api",
  "ruma-identifiers",
  "ruma-serde",
  "serde",
@@ -1625,7 +1637,7 @@ dependencies = [
 [[package]]
 name = "ruma-events"
 version = "0.22.0-alpha.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -1640,7 +1652,7 @@ dependencies = [
 [[package]]
 name = "ruma-events-macros"
 version = "0.22.0-alpha.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "proc-macro-crate",
  "proc-macro2",
@@ -1651,7 +1663,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.0.3"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "js_int",
  "ruma-api",
@@ -1666,7 +1678,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers"
 version = "0.17.4"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "rand",
  "ruma-identifiers-macros",
@@ -1678,7 +1690,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-macros"
 version = "0.17.4"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1689,18 +1701,16 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-validation"
 version = "0.1.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
- "ruma-serde",
  "serde",
- "serde_json",
  "strum",
 ]
 
 [[package]]
 name = "ruma-serde"
 version = "0.2.3"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "form_urlencoded",
  "itoa",
@@ -1712,7 +1722,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.6.0-dev.1"
-source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#195b15be25ba1f2d4e0b520f01ecb77143c01eb0"
+source = "git+https://github.com/timokoesters/ruma?branch=timo-fed-fixes#47fab87325b71b7f6c2fb3cd276d1f813e42abf7"
 dependencies = [
  "base64",
  "ring",
@@ -1734,9 +1744,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-demangle"
-version = "0.1.16"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
+checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b"
 
 [[package]]
 name = "rustc_version"
@@ -1832,18 +1842,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
-version = "1.0.115"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.115"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1852,9 +1862,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.57"
+version = "1.0.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
 dependencies = [
  "itoa",
  "ryu",
@@ -1880,16 +1890,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
 
 [[package]]
-name = "sha2"
-version = "0.9.1"
+name = "sharded-slab"
+version = "0.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
+checksum = "06d5a3f5166fb5b42a5439f2eee8b9de149e235961e3eb21c5808fc3ea17ff3e"
 dependencies = [
- "block-buffer",
- "cfg-if",
- "cpuid-bool",
- "digest",
- "opaque-debug 0.3.0",
+ "lazy_static",
 ]
 
 [[package]]
@@ -1910,12 +1916,10 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 
 [[package]]
 name = "sled"
-version = "0.32.1"
+version = "0.34.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e3dbbb8ee10611bd1d020767c27599ccbbf8365f7e0ed7e54429cc8b9433ad8"
+checksum = "f72c064e63fbca3138ad07f3588c58093f1684f3a99f60dcfa6d46b87e60fde7"
 dependencies = [
- "array-init",
- "backtrace",
  "crc32fast",
  "crossbeam-epoch",
  "crossbeam-utils",
@@ -1934,11 +1938,11 @@ checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
 
 [[package]]
 name = "socket2"
-version = "0.3.12"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
+checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "libc",
  "redox_syscall",
  "winapi 0.3.9",
@@ -1952,9 +1956,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "standback"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33a71ea1ea5f8747d1af1979bfb7e65c3a025a70609f04ceb78425bc5adad8e6"
+checksum = "f4e0831040d2cf2bdfd51b844be71885783d489898a192f254ae25d57cce725c"
 dependencies = [
  "version_check",
 ]
@@ -1965,6 +1969,22 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7345c971d1ef21ffdbd103a75990a15eb03604fc8b8852ca8cb418ee1a099028"
 
+[[package]]
+name = "state-res"
+version = "0.1.0"
+source = "git+https://github.com/timokoesters/state-res?branch=spec-comp#a7d76935f12757aecfee305838069c9bcbe7d34a"
+dependencies = [
+ "itertools",
+ "js_int",
+ "maplit",
+ "ruma",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tracing",
+ "tracing-subscriber",
+]
+
 [[package]]
 name = "stdweb"
 version = "0.4.20"
@@ -2016,18 +2036,18 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
 
 [[package]]
 name = "strum"
-version = "0.19.2"
+version = "0.19.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3924a58d165da3b7b2922c667ab0673c7b5fd52b5c19ea3442747bcb3cd15abe"
+checksum = "b89a286a7e3b5720b9a477b23253bc50debac207c8d21505f8e70b36792f11b5"
 dependencies = [
  "strum_macros",
 ]
 
 [[package]]
 name = "strum_macros"
-version = "0.19.2"
+version = "0.19.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d2ab682ecdcae7f5f45ae85cd7c1e6c8e68ea42c8a612d47fedf831c037146a"
+checksum = "e61bb0be289045cb80bfce000512e32d09f8337e54c186725da381377ad1f8d5"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -2035,17 +2055,11 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "subtle"
-version = "2.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1"
-
 [[package]]
 name = "syn"
-version = "1.0.39"
+version = "1.0.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
+checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2058,7 +2072,7 @@ version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "libc",
  "rand",
  "redox_syscall",
@@ -2068,24 +2082,33 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
+checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
+checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
 [[package]]
 name = "time"
 version = "0.1.44"
@@ -2099,11 +2122,11 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.2.16"
+version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15"
+checksum = "55b7151c9065e80917fbf285d9a5d1432f60db41d170ccafc749a136b41a93af"
 dependencies = [
- "cfg-if",
+ "const_fn",
  "libc",
  "standback",
  "stdweb",
@@ -2114,9 +2137,9 @@ dependencies = [
 
 [[package]]
 name = "time-macros"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
 dependencies = [
  "proc-macro-hack",
  "time-macros-impl",
@@ -2177,9 +2200,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.14.0"
+version = "0.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "228139ddd4fea3fa345a29233009635235833e52807af7ea6448ead03890d6a9"
+checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
 dependencies = [
  "futures-core",
  "rustls",
@@ -2213,9 +2236,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.5.6"
+version = "0.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
 dependencies = [
  "serde",
 ]
@@ -2228,35 +2251,125 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
 
 [[package]]
 name = "tracing"
-version = "0.1.19"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c"
+checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "log",
+ "pin-project-lite",
+ "tracing-attributes",
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-attributes"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "tracing-core"
-version = "0.1.15"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f0e00789804e99b20f12bc7003ca416309d28a6f495d6af58d1e2c2842461b5"
+checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
 dependencies = [
  "lazy_static",
 ]
 
 [[package]]
-name = "try-lock"
-version = "0.2.3"
+name = "tracing-log"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+checksum = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef0a5e15477aa303afbfac3a44cba9b6430fdaad52423b1e6c0dbbe28c3eedd"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "trust-dns-proto"
+version = "0.19.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28"
+dependencies = [
+ "async-trait",
+ "backtrace",
+ "enum-as-inner",
+ "futures",
+ "idna",
+ "lazy_static",
+ "log",
+ "rand",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "url",
+]
 
 [[package]]
-name = "typenum"
-version = "1.12.0"
+name = "trust-dns-resolver"
+version = "0.19.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77"
+dependencies = [
+ "backtrace",
+ "cfg-if 0.1.10",
+ "futures",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "resolv-conf",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "trust-dns-proto",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
 
 [[package]]
 name = "unicase"
@@ -2297,16 +2410,6 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
-[[package]]
-name = "universal-hash"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
 [[package]]
 name = "untrusted"
 version = "0.7.1"
@@ -2360,11 +2463,11 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
+checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "serde",
  "serde_json",
  "wasm-bindgen-macro",
@@ -2372,9 +2475,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0"
+checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -2387,11 +2490,11 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.17"
+version = "0.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699"
+checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da"
 dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -2399,9 +2502,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2"
+checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2409,9 +2512,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556"
+checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2422,15 +2525,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
+checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
 
 [[package]]
 name = "web-sys"
-version = "0.3.44"
+version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47"
+checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -2446,6 +2549,18 @@ dependencies = [
  "untrusted",
 ]
 
+[[package]]
+name = "weezl"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0e26e7a4d998e3d7949c69444b8b4916bac810da0d3a82ae612c89e952782f4"
+
+[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
 [[package]]
 name = "winapi"
 version = "0.2.8"
@@ -2480,6 +2595,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "winreg"
 version = "0.7.0"
diff --git a/Cargo.toml b/Cargo.toml
index 4945e3c88a3c01343b067a73d8b598b1c6d1e506..2126e42786c3420de58fbc92017e73c1c891cdab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,27 +12,52 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+# Used to handle requests
 # 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 = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", features = ["tls"] }
-
-#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
-ruma = { git = "https://github.com/timokoesters/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "timo-fixes" } # Used for matrix spec type definitions and helpers
-#ruma = { path = "../ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
-tokio = "0.2.22" # Used for long polling
-sled = "0.32.0" # Used for storing data permanently
-log = "0.4.8" # Used for emitting log entries
-http = "0.2.1" # Used for rocket<->ruma conversions
-directories = "2.0.2" # Used to find data directory for default db path
-js_int = "0.1.5" # Used for number types for ruma
-serde_json = { version = "1.0.53", features = ["raw_value"] } # Used for ruma wrapper
-serde = "1.0.111" # Used for pdu definition
-rand = "0.7.3" # Used for secure identifiers
-rust-argon2 = "0.8.2" # Used to hash passwords
-reqwest = "0.10.6" # Used to send requests
-thiserror = "1.0.19" # Used for conduit::Error type
-image = { version = "0.23.4", default-features = false, features = ["jpeg", "png", "gif"] } # Used to generate thumbnails for images
-base64 = "0.12.3" # Used to encode server public key
+#rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", default-features = false, features = ["tls"] } # Used to handle requests
+rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", default-features = false, features = ["tls"] }
+
+# Used for matrix spec type definitions and helpers
+#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "aff914050eb297bd82b8aafb12158c88a9e480e1" }
+ruma = { git = "https://github.com/timokoesters/ruma", features = ["rand", "client-api", "federation-api", "unstable-exhaustive-types", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "timo-fed-fixes" }
+#ruma = { path = "../ruma/ruma", features = ["unstable-exhaustive-types", "rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
+
+# Used when doing state resolution
+state-res = { git = "https://github.com/timokoesters/state-res", branch = "spec-comp", features = ["unstable-pre-spec"] }
+#state-res = { path = "../state-res", features = ["unstable-pre-spec"] }
+
+# Used for long polling
+tokio = "0.2.22"
+# Used for storing data permanently
+sled = "0.34.4"
+# Used for emitting log entries
+log = "0.4.11"
+# Used for rocket<->ruma conversions
+http = "0.2.1"
+# Used to find data directory for default db path
+directories = "3.0.1"
+# Used for number types for ruma
+js_int = "0.1.9"
+# Used for ruma wrapper
+serde_json = { version = "1.0.57", features = ["raw_value"] }
+# Used for pdu definition
+serde = "1.0.116"
+# Used for secure identifiers
+rand = "0.7.3"
+# Used to hash passwords
+rust-argon2 = "0.8.2"
+# Used to send requests
+reqwest = "0.10.8"
+# Used for conduit::Error type
+thiserror = "1.0.20"
+# Used to generate thumbnails for images
+image = { version = "0.23.9", default-features = false, features = ["jpeg", "png", "gif"] }
+# Used to encode server public key
+base64 = "0.12.3"
+# Used when hashing the state
+ring = "0.16.15"
+# Used when querying the SRV record of other servers
+trust-dns-resolver = "0.19.5"
 
 [features]
 default = ["conduit_bin"]
diff --git a/DEPLOY_FROM_SOURCE.md b/DEPLOY_FROM_SOURCE.md
index 4d685f6e0ce1987de915c22305359089c832cd9c..456fe6ea08a41e91b1c659844b96eca1107c2704 100644
--- a/DEPLOY_FROM_SOURCE.md
+++ b/DEPLOY_FROM_SOURCE.md
@@ -27,7 +27,10 @@ ## Setup systemd service
 
 Environment="ROCKET_PORT=14004" # Reverse proxy port
 
+#Environment="ROCKET_MAX_REQUEST_SIZE=20000000" # in bytes
 #Environment="ROCKET_REGISTRATION_DISABLED=true"
+#Environment="ROCKET_ENCRYPTION_DISABLED=true"
+#Environment="ROCKET_FEDERATION_ENABLED=true"
 #Environment="ROCKET_LOG=normal" # Detailed logging
 
 Environment="ROCKET_ENV=production"
diff --git a/Rocket-example.toml b/Rocket-example.toml
index 41b36d3a9ba54d2649e7aef16d20e9c958b2e405..8eb48e95f3cb3285eca50b7a55ca535e564455e3 100644
--- a/Rocket-example.toml
+++ b/Rocket-example.toml
@@ -16,6 +16,8 @@ port = 14004
 # Note: existing rooms will continue to work
 #encryption_disabled = true
 
+#federation_enabled = true
+
 # Default path is in this user's data
 #database_path = "/home/timo/MyConduitServer"
 
diff --git a/docker-compose.yml b/docker-compose.yml
index f06eaca9729a9a707a3c6e487cdca44f10dd408c..7d19762256f509cb7db98a2db4932e6b20bd175d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -31,6 +31,7 @@ services:
             # ROCKET_PORT: 8000
             # ROCKET_REGISTRATION_DISABLED: 'true'
             # ROCKET_ENCRYPTION_DISABLED: 'true'
+            # ROCKET_FEDERATION_ENABLED: 'true'
             # ROCKET_DATABASE_PATH: /srv/conduit/.local/share/conduit
             # ROCKET_WORKERS: 10
             # ROCKET_MAX_REQUEST_SIZE: 20_000_000  # in bytes, ~20 MB
diff --git a/rust-toolchain b/rust-toolchain
index 50aceaa7b715f2941dfceb6818906fe782321479..21998d3c2d9ec6777d9a65749826f4b33972abfd 100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1 +1 @@
-1.45.0
+1.47.0
diff --git a/src/client_server/account.rs b/src/client_server/account.rs
index 9837d1b95e7350a7179d556d7da2da28ebfc04e4..66b4a62a665024dc454cbddad75b4f87d738c714 100644
--- a/src/client_server/account.rs
+++ b/src/client_server/account.rs
@@ -1,3 +1,5 @@
+use std::{collections::BTreeMap, convert::TryInto};
+
 use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
 use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma};
 use ruma::{
@@ -11,8 +13,11 @@
             uiaa::{AuthFlow, UiaaInfo},
         },
     },
-    events::{room::member, EventType},
-    UserId,
+    events::{
+        room::canonical_alias, room::guest_access, room::history_visibility, room::join_rules,
+        room::member, room::name, room::topic, EventType,
+    },
+    RoomAliasId, RoomId, RoomVersionId, UserId,
 };
 
 use register::RegistrationKind;
@@ -33,7 +38,7 @@
 )]
 pub fn get_register_available_route(
     db: State<'_, Database>,
-    body: Ruma<get_username_availability::Request>,
+    body: Ruma<get_username_availability::Request<'_>>,
 ) -> ConduitResult<get_username_availability::Response> {
     // Validate user id
     let user_id = UserId::parse_with_server_name(body.username.clone(), db.globals.server_name())
@@ -73,9 +78,9 @@ pub fn get_register_available_route(
     feature = "conduit_bin",
     post("/_matrix/client/r0/register", data = "<body>")
 )]
-pub fn register_route(
+pub async fn register_route(
     db: State<'_, Database>,
-    body: Ruma<register::Request>,
+    body: Ruma<register::Request<'_>>,
 ) -> ConduitResult<register::Response> {
     if db.globals.registration_disabled() {
         return Err(Error::BadRequest(
@@ -84,7 +89,7 @@ pub fn register_route(
         ));
     }
 
-    let is_guest = matches!(body.kind, Some(RegistrationKind::Guest));
+    let is_guest = body.kind == RegistrationKind::Guest;
 
     let mut missing_username = false;
 
@@ -202,6 +207,265 @@ pub fn register_route(
         body.initial_device_display_name.clone(),
     )?;
 
+    // If this is the first user on this server, create the admins room
+    if db.users.count() == 1 {
+        // Create a user for the server
+        let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name())
+            .expect("@conduit:server_name is valid");
+
+        db.users.create(&conduit_user, "")?;
+
+        let room_id = RoomId::new(db.globals.server_name());
+
+        let mut content = ruma::events::room::create::CreateEventContent::new(conduit_user.clone());
+        content.federate = true;
+        content.predecessor = None;
+        content.room_version = RoomVersionId::Version6;
+
+        // 1. The room create event
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomCreate,
+                content: serde_json::to_value(content).expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 2. Make conduit bot join
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomMember,
+                content: serde_json::to_value(member::MemberEventContent {
+                    membership: member::MembershipState::Join,
+                    displayname: None,
+                    avatar_url: None,
+                    is_direct: None,
+                    third_party_invite: None,
+                })
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some(conduit_user.to_string()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 3. Power levels
+        let mut users = BTreeMap::new();
+        users.insert(conduit_user.clone(), 100.into());
+        users.insert(user_id.clone(), 100.into());
+
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomPowerLevels,
+                content: serde_json::to_value(
+                    ruma::events::room::power_levels::PowerLevelsEventContent {
+                        ban: 50.into(),
+                        events: BTreeMap::new(),
+                        events_default: 0.into(),
+                        invite: 50.into(),
+                        kick: 50.into(),
+                        redact: 50.into(),
+                        state_default: 50.into(),
+                        users,
+                        users_default: 0.into(),
+                        notifications: ruma::events::room::power_levels::NotificationPowerLevels {
+                            room: 50.into(),
+                        },
+                    },
+                )
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 4.1 Join Rules
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomJoinRules,
+                content: serde_json::to_value(join_rules::JoinRulesEventContent::new(
+                    join_rules::JoinRule::Invite,
+                ))
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 4.2 History Visibility
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomHistoryVisibility,
+                content: serde_json::to_value(
+                    history_visibility::HistoryVisibilityEventContent::new(
+                        history_visibility::HistoryVisibility::Shared,
+                    ),
+                )
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 4.3 Guest Access
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomGuestAccess,
+                content: serde_json::to_value(guest_access::GuestAccessEventContent::new(
+                    guest_access::GuestAccess::Forbidden,
+                ))
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // 6. Events implied by name and topic
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomName,
+                content: serde_json::to_value(
+                    name::NameEventContent::new("Admin Room".to_owned()).map_err(|_| {
+                        Error::BadRequest(ErrorKind::InvalidParam, "Name is invalid.")
+                    })?,
+                )
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomTopic,
+                content: serde_json::to_value(topic::TopicEventContent {
+                    topic: format!("Manage {}", db.globals.server_name()),
+                })
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        // Room alias
+        let alias: RoomAliasId = format!("#admins:{}", db.globals.server_name())
+            .try_into()
+            .expect("#admins:server_name is a valid alias name");
+
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomCanonicalAlias,
+                content: serde_json::to_value(canonical_alias::CanonicalAliasEventContent {
+                    alias: Some(alias.clone()),
+                    alt_aliases: Vec::new(),
+                })
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some("".to_owned()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+
+        db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
+
+        // Invite and join the real user
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomMember,
+                content: serde_json::to_value(member::MemberEventContent {
+                    membership: member::MembershipState::Invite,
+                    displayname: None,
+                    avatar_url: None,
+                    is_direct: None,
+                    third_party_invite: None,
+                })
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some(user_id.to_string()),
+                redacts: None,
+            },
+            &conduit_user,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomMember,
+                content: serde_json::to_value(member::MemberEventContent {
+                    membership: member::MembershipState::Join,
+                    displayname: None,
+                    avatar_url: None,
+                    is_direct: None,
+                    third_party_invite: None,
+                })
+                .expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some(user_id.to_string()),
+                redacts: None,
+            },
+            &user_id,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+    }
+
     Ok(register::Response {
         access_token: Some(token),
         user_id,
@@ -223,7 +487,7 @@ pub fn register_route(
 )]
 pub fn change_password_route(
     db: State<'_, Database>,
-    body: Ruma<change_password::Request>,
+    body: Ruma<change_password::Request<'_>>,
 ) -> ConduitResult<change_password::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -303,9 +567,9 @@ pub fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::Respon
     feature = "conduit_bin",
     post("/_matrix/client/r0/account/deactivate", data = "<body>")
 )]
-pub fn deactivate_route(
+pub async fn deactivate_route(
     db: State<'_, Database>,
-    body: Ruma<deactivate::Request>,
+    body: Ruma<deactivate::Request<'_>>,
 ) -> ConduitResult<deactivate::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -354,17 +618,18 @@ pub fn deactivate_route(
             third_party_invite: None,
         };
 
-        db.rooms.append_pdu(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomMember,
                 content: serde_json::to_value(event).expect("event is valid, we just created it"),
                 unsigned: None,
                 state_key: Some(sender_id.to_string()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
diff --git a/src/client_server/alias.rs b/src/client_server/alias.rs
index a0293881667d95f4ac431233054d6de8d11550e8..c2c3eb9c5d16dcba6bf342f780f1c49d83a5343e 100644
--- a/src/client_server/alias.rs
+++ b/src/client_server/alias.rs
@@ -1,11 +1,14 @@
 use super::State;
 use crate::{server_server, ConduitResult, Database, Error, Ruma};
-use ruma::api::{
-    client::{
-        error::ErrorKind,
-        r0::alias::{create_alias, delete_alias, get_alias},
+use ruma::{
+    api::{
+        client::{
+            error::ErrorKind,
+            r0::alias::{create_alias, delete_alias, get_alias},
+        },
+        federation,
     },
-    federation,
+    RoomAliasId,
 };
 
 #[cfg(feature = "conduit_bin")]
@@ -17,7 +20,7 @@
 )]
 pub fn create_alias_route(
     db: State<'_, Database>,
-    body: Ruma<create_alias::IncomingRequest>,
+    body: Ruma<create_alias::Request<'_>>,
 ) -> ConduitResult<create_alias::Response> {
     if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
         return Err(Error::Conflict("Alias already exists."));
@@ -26,7 +29,7 @@ pub fn create_alias_route(
     db.rooms
         .set_alias(&body.room_alias, Some(&body.room_id), &db.globals)?;
 
-    Ok(create_alias::Response.into())
+    Ok(create_alias::Response::new().into())
 }
 
 #[cfg_attr(
@@ -35,11 +38,11 @@ pub fn create_alias_route(
 )]
 pub fn delete_alias_route(
     db: State<'_, Database>,
-    body: Ruma<delete_alias::IncomingRequest>,
+    body: Ruma<delete_alias::Request<'_>>,
 ) -> ConduitResult<delete_alias::Response> {
     db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
 
-    Ok(delete_alias::Response.into())
+    Ok(delete_alias::Response::new().into())
 }
 
 #[cfg_attr(
@@ -48,36 +51,33 @@ pub fn delete_alias_route(
 )]
 pub async fn get_alias_route(
     db: State<'_, Database>,
-    body: Ruma<get_alias::IncomingRequest>,
+    body: Ruma<get_alias::Request<'_>>,
 ) -> ConduitResult<get_alias::Response> {
-    if body.room_alias.server_name() != db.globals.server_name() {
+    get_alias_helper(&db, &body.room_alias).await
+}
+
+pub async fn get_alias_helper(
+    db: &Database,
+    room_alias: &RoomAliasId,
+) -> ConduitResult<get_alias::Response> {
+    if room_alias.server_name() != db.globals.server_name() {
         let response = server_server::send_request(
-            &db,
-            body.room_alias.server_name().to_string(),
-            federation::query::get_room_information::v1::Request {
-                room_alias: body.room_alias.to_string(),
-            },
+            &db.globals,
+            room_alias.server_name().to_owned(),
+            federation::query::get_room_information::v1::Request { room_alias },
         )
         .await?;
 
-        return Ok(get_alias::Response {
-            room_id: response.room_id,
-            servers: response.servers,
-        }
-        .into());
+        return Ok(get_alias::Response::new(response.room_id, response.servers).into());
     }
 
     let room_id = db
         .rooms
-        .id_from_alias(&body.room_alias)?
+        .id_from_alias(&room_alias)?
         .ok_or(Error::BadRequest(
             ErrorKind::NotFound,
             "Room with alias not found.",
         ))?;
 
-    Ok(get_alias::Response {
-        room_id,
-        servers: vec![db.globals.server_name().to_string()],
-    }
-    .into())
+    Ok(get_alias::Response::new(room_id, vec![db.globals.server_name().to_owned()]).into())
 }
diff --git a/src/client_server/backup.rs b/src/client_server/backup.rs
index 9994f191e13297273c12f171950af23318b3ac1c..5d9a9250323229f8d6c497406e56bc860dd7ba69 100644
--- a/src/client_server/backup.rs
+++ b/src/client_server/backup.rs
@@ -35,7 +35,7 @@ pub fn create_backup_route(
 )]
 pub fn update_backup_route(
     db: State<'_, Database>,
-    body: Ruma<update_backup::Request>,
+    body: Ruma<update_backup::Request<'_>>,
 ) -> ConduitResult<update_backup::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     db.key_backups
@@ -77,7 +77,7 @@ pub fn get_latest_backup_route(
 )]
 pub fn get_backup_route(
     db: State<'_, Database>,
-    body: Ruma<get_backup::Request>,
+    body: Ruma<get_backup::Request<'_>>,
 ) -> ConduitResult<get_backup::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let algorithm = db
@@ -92,7 +92,7 @@ pub fn get_backup_route(
         algorithm,
         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(),
+        version: body.version.to_owned(),
     }
     .into())
 }
@@ -119,7 +119,7 @@ pub fn delete_backup_route(
 )]
 pub fn add_backup_keys_route(
     db: State<'_, Database>,
-    body: Ruma<add_backup_keys::Request>,
+    body: Ruma<add_backup_keys::Request<'_>>,
 ) -> ConduitResult<add_backup_keys::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -205,7 +205,7 @@ pub fn add_backup_key_session_route(
 )]
 pub fn get_backup_keys_route(
     db: State<'_, Database>,
-    body: Ruma<get_backup_keys::Request>,
+    body: Ruma<get_backup_keys::Request<'_>>,
 ) -> ConduitResult<get_backup_keys::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
diff --git a/src/client_server/config.rs b/src/client_server/config.rs
index 8cb6a0d7257a3c6ee84d58463bc6efc89c2bb288..515ad16075174f97eb0423d1dedc5d19408105c2 100644
--- a/src/client_server/config.rs
+++ b/src/client_server/config.rs
@@ -5,10 +5,9 @@
         error::ErrorKind,
         r0::config::{get_global_account_data, set_global_account_data},
     },
-    events::{custom::CustomEventContent, BasicEvent, EventType},
+    events::{custom::CustomEventContent, BasicEvent},
     Raw,
 };
-use std::convert::TryFrom;
 
 #[cfg(feature = "conduit_bin")]
 use rocket::{get, put};
@@ -19,7 +18,7 @@
 )]
 pub fn set_global_account_data_route(
     db: State<'_, Database>,
-    body: Ruma<set_global_account_data::Request>,
+    body: Ruma<set_global_account_data::Request<'_>>,
 ) -> ConduitResult<set_global_account_data::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -50,17 +49,13 @@ pub fn set_global_account_data_route(
 )]
 pub fn get_global_account_data_route(
     db: State<'_, Database>,
-    body: Ruma<get_global_account_data::Request>,
+    body: Ruma<get_global_account_data::Request<'_>>,
 ) -> ConduitResult<get_global_account_data::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     let data = db
         .account_data
-        .get::<Raw<ruma::events::AnyBasicEvent>>(
-            None,
-            sender_id,
-            EventType::try_from(&body.event_type).expect("EventType::try_from can never fail"),
-        )?
+        .get::<Raw<ruma::events::AnyBasicEvent>>(None, sender_id, body.event_type.clone().into())?
         .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
 
     Ok(get_global_account_data::Response { account_data: data }.into())
diff --git a/src/client_server/context.rs b/src/client_server/context.rs
index 7a6cbce0aed47502742500adffa131ebabd5e565..4c9be20fbd6a717a039f1fcc493884a4ddf56e9d 100644
--- a/src/client_server/context.rs
+++ b/src/client_server/context.rs
@@ -12,7 +12,7 @@
 )]
 pub fn get_context_route(
     db: State<'_, Database>,
-    body: Ruma<get_context::Request>,
+    body: Ruma<get_context::Request<'_>>,
 ) -> ConduitResult<get_context::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -49,7 +49,10 @@ pub fn get_context_route(
         .filter_map(|r| r.ok()) // Remove buggy events
         .collect::<Vec<_>>();
 
-    let start_token = events_before.last().map(|(count, _)| count.to_string());
+    let start_token = events_before
+        .last()
+        .and_then(|(pdu_id, _)| db.rooms.pdu_count(pdu_id).ok())
+        .map(|count| count.to_string());
 
     let events_before = events_before
         .into_iter()
@@ -68,25 +71,28 @@ pub fn get_context_route(
         .filter_map(|r| r.ok()) // Remove buggy events
         .collect::<Vec<_>>();
 
-    let end_token = events_after.last().map(|(count, _)| count.to_string());
+    let end_token = events_after
+        .last()
+        .and_then(|(pdu_id, _)| db.rooms.pdu_count(pdu_id).ok())
+        .map(|count| count.to_string());
 
     let events_after = events_after
         .into_iter()
         .map(|(_, pdu)| pdu.to_room_event())
         .collect::<Vec<_>>();
 
-    Ok(get_context::Response {
-        start: start_token,
-        end: end_token,
-        events_before,
-        event: Some(base_event),
-        events_after,
-        state: db // TODO: State at event
-            .rooms
-            .room_state_full(&body.room_id)?
-            .values()
-            .map(|pdu| pdu.to_state_event())
-            .collect(),
-    }
-    .into())
+    let mut resp = get_context::Response::new();
+    resp.start = start_token;
+    resp.end = end_token;
+    resp.events_before = events_before;
+    resp.event = Some(base_event);
+    resp.events_after = events_after;
+    resp.state = db // TODO: State at event
+        .rooms
+        .room_state_full(&body.room_id)?
+        .values()
+        .map(|pdu| pdu.to_state_event())
+        .collect();
+
+    Ok(resp.into())
 }
diff --git a/src/client_server/device.rs b/src/client_server/device.rs
index 379f827eada62c65e4401c1aa28ece97f90618b7..6352d0d1d4f84574016f0fe178dfb3d8550d71fb 100644
--- a/src/client_server/device.rs
+++ b/src/client_server/device.rs
@@ -37,7 +37,7 @@ pub fn get_devices_route(
 )]
 pub fn get_device_route(
     db: State<'_, Database>,
-    body: Ruma<get_device::Request>,
+    body: Ruma<get_device::Request<'_>>,
     _device_id: String,
 ) -> ConduitResult<get_device::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@@ -56,7 +56,7 @@ pub fn get_device_route(
 )]
 pub fn update_device_route(
     db: State<'_, Database>,
-    body: Ruma<update_device::Request>,
+    body: Ruma<update_device::Request<'_>>,
     _device_id: String,
 ) -> ConduitResult<update_device::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@@ -80,7 +80,7 @@ pub fn update_device_route(
 )]
 pub fn delete_device_route(
     db: State<'_, Database>,
-    body: Ruma<delete_device::Request>,
+    body: Ruma<delete_device::Request<'_>>,
     _device_id: String,
 ) -> ConduitResult<delete_device::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
@@ -127,7 +127,7 @@ pub fn delete_device_route(
 )]
 pub fn delete_devices_route(
     db: State<'_, Database>,
-    body: Ruma<delete_devices::Request>,
+    body: Ruma<delete_devices::Request<'_>>,
 ) -> ConduitResult<delete_devices::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
diff --git a/src/client_server/directory.rs b/src/client_server/directory.rs
index 26188f733ecab064e1767d7ec672f53417d1844f..c82a15f02db63bce8bc4c8069fd403e822aad152 100644
--- a/src/client_server/directory.rs
+++ b/src/client_server/directory.rs
@@ -6,7 +6,7 @@
             error::ErrorKind,
             r0::{
                 directory::{
-                    self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
+                    get_public_rooms, get_public_rooms_filtered, get_room_visibility,
                     set_room_visibility,
                 },
                 room,
@@ -14,11 +14,14 @@
         },
         federation,
     },
+    directory::Filter,
+    directory::RoomNetwork,
+    directory::{IncomingFilter, IncomingRoomNetwork, PublicRoomsChunk},
     events::{
         room::{avatar, canonical_alias, guest_access, history_visibility, name, topic},
         EventType,
     },
-    Raw,
+    Raw, ServerName,
 };
 
 #[cfg(feature = "conduit_bin")]
@@ -30,20 +33,103 @@
 )]
 pub async fn get_public_rooms_filtered_route(
     db: State<'_, Database>,
-    body: Ruma<get_public_rooms_filtered::IncomingRequest>,
+    body: Ruma<get_public_rooms_filtered::Request<'_>>,
 ) -> ConduitResult<get_public_rooms_filtered::Response> {
-    if let Some(other_server) = body
-        .server
+    get_public_rooms_filtered_helper(
+        &db,
+        body.server.as_deref(),
+        body.limit,
+        body.since.as_deref(),
+        &body.filter,
+        &body.room_network,
+    )
+    .await
+}
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/client/r0/publicRooms", data = "<body>")
+)]
+pub async fn get_public_rooms_route(
+    db: State<'_, Database>,
+    body: Ruma<get_public_rooms::Request<'_>>,
+) -> ConduitResult<get_public_rooms::Response> {
+    let response = get_public_rooms_filtered_helper(
+        &db,
+        body.server.as_deref(),
+        body.limit,
+        body.since.as_deref(),
+        &IncomingFilter::default(),
+        &IncomingRoomNetwork::Matrix,
+    )
+    .await?
+    .0;
+
+    Ok(get_public_rooms::Response {
+        chunk: response.chunk,
+        prev_batch: response.prev_batch,
+        next_batch: response.next_batch,
+        total_room_count_estimate: response.total_room_count_estimate,
+    }
+    .into())
+}
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    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<'_>>,
+) -> ConduitResult<set_room_visibility::Response> {
+    match body.visibility {
+        room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?,
+        room::Visibility::Private => db.rooms.set_public(&body.room_id, false)?,
+    }
+
+    Ok(set_room_visibility::Response.into())
+}
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    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<'_>>,
+) -> ConduitResult<get_room_visibility::Response> {
+    Ok(get_room_visibility::Response {
+        visibility: if db.rooms.is_public_room(&body.room_id)? {
+            room::Visibility::Public
+        } else {
+            room::Visibility::Private
+        },
+    }
+    .into())
+}
+
+pub async fn get_public_rooms_filtered_helper(
+    db: &Database,
+    server: Option<&ServerName>,
+    limit: Option<js_int::UInt>,
+    since: Option<&str>,
+    filter: &IncomingFilter,
+    _network: &IncomingRoomNetwork,
+) -> ConduitResult<get_public_rooms_filtered::Response> {
+    if let Some(other_server) = server
         .clone()
-        .filter(|server| server != &db.globals.server_name().as_str())
+        .filter(|server| *server != db.globals.server_name().as_str())
     {
         let response = server_server::send_request(
-            &db,
-            other_server,
-            federation::directory::get_public_rooms::v1::Request {
-                limit: body.limit,
-                since: body.since.clone(),
-                room_network: federation::directory::get_public_rooms::v1::RoomNetwork::Matrix,
+            &db.globals,
+            other_server.to_owned(),
+            federation::directory::get_public_rooms_filtered::v1::Request {
+                limit,
+                since: since.as_deref(),
+                filter: Filter {
+                    generic_search_term: filter.generic_search_term.as_deref(),
+                },
+                room_network: RoomNetwork::Matrix,
             },
         )
         .await?;
@@ -72,10 +158,10 @@ pub async fn get_public_rooms_filtered_route(
         .into());
     }
 
-    let limit = body.limit.map_or(10, u64::from);
-    let mut since = 0_u64;
+    let limit = limit.map_or(10, u64::from);
+    let mut num_since = 0_u64;
 
-    if let Some(s) = &body.since {
+    if let Some(s) = &since {
         let mut characters = s.chars();
         let backwards = match characters.next() {
             Some('n') => false,
@@ -88,13 +174,13 @@ pub async fn get_public_rooms_filtered_route(
             }
         };
 
-        since = characters
+        num_since = characters
             .collect::<String>()
             .parse()
             .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token."))?;
 
         if backwards {
-            since = since.saturating_sub(limit);
+            num_since = num_since.saturating_sub(limit);
         }
     }
 
@@ -107,7 +193,7 @@ pub async fn get_public_rooms_filtered_route(
                 // TODO: Do not load full state?
                 let state = db.rooms.room_state_full(&room_id)?;
 
-                let chunk = directory::PublicRoomsChunk {
+                let chunk = PublicRoomsChunk {
                     aliases: Vec::new(),
                     canonical_alias: state
                         .get(&(EventType::RoomCanonicalAlias, "".to_owned()))
@@ -216,20 +302,20 @@ pub async fn get_public_rooms_filtered_route(
 
     let chunk = all_rooms
         .into_iter()
-        .skip(since as usize)
+        .skip(num_since as usize)
         .take(limit as usize)
         .collect::<Vec<_>>();
 
-    let prev_batch = if since == 0 {
+    let prev_batch = if num_since == 0 {
         None
     } else {
-        Some(format!("p{}", since))
+        Some(format!("p{}", num_since))
     };
 
     let next_batch = if chunk.len() < limit as usize {
         None
     } else {
-        Some(format!("n{}", since + limit))
+        Some(format!("n{}", num_since + limit))
     };
 
     Ok(get_public_rooms_filtered::Response {
@@ -240,89 +326,3 @@ pub async fn get_public_rooms_filtered_route(
     }
     .into())
 }
-
-#[cfg_attr(
-    feature = "conduit_bin",
-    get("/_matrix/client/r0/publicRooms", data = "<body>")
-)]
-pub async fn get_public_rooms_route(
-    db: State<'_, Database>,
-    body: Ruma<get_public_rooms::IncomingRequest>,
-) -> ConduitResult<get_public_rooms::Response> {
-    let Ruma {
-        body:
-            get_public_rooms::IncomingRequest {
-                limit,
-                server,
-                since,
-            },
-        sender_id,
-        device_id,
-        json_body,
-    } = body;
-
-    let get_public_rooms_filtered::Response {
-        chunk,
-        prev_batch,
-        next_batch,
-        total_room_count_estimate,
-    } = get_public_rooms_filtered_route(
-        db,
-        Ruma {
-            body: get_public_rooms_filtered::IncomingRequest {
-                filter: None,
-                limit,
-                room_network: get_public_rooms_filtered::RoomNetwork::Matrix,
-                server,
-                since,
-            },
-            sender_id,
-            device_id,
-            json_body,
-        },
-    )
-    .await?
-    .0;
-
-    Ok(get_public_rooms::Response {
-        chunk,
-        prev_batch,
-        next_batch,
-        total_room_count_estimate,
-    }
-    .into())
-}
-
-#[cfg_attr(
-    feature = "conduit_bin",
-    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>,
-) -> ConduitResult<set_room_visibility::Response> {
-    match body.visibility {
-        room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?,
-        room::Visibility::Private => db.rooms.set_public(&body.room_id, false)?,
-    }
-
-    Ok(set_room_visibility::Response.into())
-}
-
-#[cfg_attr(
-    feature = "conduit_bin",
-    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>,
-) -> ConduitResult<get_room_visibility::Response> {
-    Ok(get_room_visibility::Response {
-        visibility: if db.rooms.is_public_room(&body.room_id)? {
-            room::Visibility::Public
-        } else {
-            room::Visibility::Private
-        },
-    }
-    .into())
-}
diff --git a/src/client_server/filter.rs b/src/client_server/filter.rs
index 165419a81de27f7ca902b519a7dc5c0f6833df29..4b1c3a000e2f1a058e6db26a3f986f95730d523a 100644
--- a/src/client_server/filter.rs
+++ b/src/client_server/filter.rs
@@ -7,23 +7,18 @@
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/filter/<_>"))]
 pub fn get_filter_route() -> ConduitResult<get_filter::Response> {
     // TODO
-    Ok(get_filter::Response {
-        filter: filter::FilterDefinition {
-            event_fields: None,
-            event_format: None,
-            account_data: None,
-            room: None,
-            presence: None,
-        },
-    }
+    Ok(get_filter::Response::new(filter::IncomingFilterDefinition {
+        event_fields: None,
+        event_format: None,
+        account_data: None,
+        room: None,
+        presence: None,
+    })
     .into())
 }
 
 #[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/user/<_>/filter"))]
 pub fn create_filter_route() -> ConduitResult<create_filter::Response> {
     // TODO
-    Ok(create_filter::Response {
-        filter_id: utils::random_string(10),
-    }
-    .into())
+    Ok(create_filter::Response::new(utils::random_string(10)).into())
 }
diff --git a/src/client_server/keys.rs b/src/client_server/keys.rs
index f88878ce68d0aa476b268e3a41a2d3d2e6ac475b..0e7b1eff1623ed6d9fc485761bfddd59e023a60f 100644
--- a/src/client_server/keys.rs
+++ b/src/client_server/keys.rs
@@ -11,7 +11,7 @@
             uiaa::{AuthFlow, UiaaInfo},
         },
     },
-    encryption::UnsignedDeviceInfo,
+    encryption::IncomingUnsignedDeviceInfo,
 };
 use std::collections::{BTreeMap, HashSet};
 
@@ -24,7 +24,7 @@
 )]
 pub fn upload_keys_route(
     db: State<'_, Database>,
-    body: Ruma<upload_keys::Request>,
+    body: Ruma<upload_keys::Request<'_>>,
 ) -> ConduitResult<upload_keys::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -56,7 +56,7 @@ pub fn upload_keys_route(
 )]
 pub fn get_keys_route(
     db: State<'_, Database>,
-    body: Ruma<get_keys::IncomingRequest>,
+    body: Ruma<get_keys::Request<'_>>,
 ) -> ConduitResult<get_keys::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -78,9 +78,9 @@ pub fn get_keys_route(
                             Error::bad_database("all_device_keys contained nonexistent device.")
                         })?;
 
-                    keys.unsigned = Some(UnsignedDeviceInfo {
+                    keys.unsigned = IncomingUnsignedDeviceInfo {
                         device_display_name: metadata.display_name,
-                    });
+                    };
 
                     container.insert(device_id, keys);
                 }
@@ -97,9 +97,9 @@ pub fn get_keys_route(
                         ),
                     )?;
 
-                    keys.unsigned = Some(UnsignedDeviceInfo {
+                    keys.unsigned = IncomingUnsignedDeviceInfo {
                         device_display_name: metadata.display_name,
-                    });
+                    };
 
                     container.insert(device_id.clone(), keys);
                 }
@@ -167,7 +167,7 @@ pub fn claim_keys_route(
 )]
 pub fn upload_signing_keys_route(
     db: State<'_, Database>,
-    body: Ruma<upload_signing_keys::Request>,
+    body: Ruma<upload_signing_keys::Request<'_>>,
 ) -> ConduitResult<upload_signing_keys::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -280,7 +280,7 @@ pub fn upload_signatures_route(
 )]
 pub fn get_key_changes_route(
     db: State<'_, Database>,
-    body: Ruma<get_key_changes::IncomingRequest>,
+    body: Ruma<get_key_changes::Request<'_>>,
 ) -> ConduitResult<get_key_changes::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
diff --git a/src/client_server/media.rs b/src/client_server/media.rs
index efcb3a6d3a249a1cb0975572a5cdfa8ebdae569e..551546bd10acd0e1ae5c96b4de6f19254f1613b3 100644
--- a/src/client_server/media.rs
+++ b/src/client_server/media.rs
@@ -1,5 +1,7 @@
 use super::State;
-use crate::{database::media::FileMeta, utils, ConduitResult, Database, Error, Ruma};
+use crate::{
+    database::media::FileMeta, server_server, utils, ConduitResult, Database, Error, Ruma,
+};
 use ruma::api::client::{
     error::ErrorKind,
     r0::media::{create_content, get_content, get_content_thumbnail, get_media_config},
@@ -9,7 +11,7 @@
 use rocket::{get, post};
 use std::convert::TryInto;
 
-const MXC_LENGTH: usize = 256;
+const MXC_LENGTH: usize = 32;
 
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))]
 pub fn get_media_config_route(
@@ -27,7 +29,7 @@ pub fn get_media_config_route(
 )]
 pub fn create_content_route(
     db: State<'_, Database>,
-    body: Ruma<create_content::Request>,
+    body: Ruma<create_content::Request<'_>>,
 ) -> ConduitResult<create_content::Response> {
     let mxc = format!(
         "mxc://{}/{}",
@@ -36,7 +38,7 @@ pub fn create_content_route(
     );
     db.media.create(
         mxc.clone(),
-        body.filename.as_ref(),
+        &body.filename.as_deref(),
         &body.content_type,
         &body.file,
     )?;
@@ -46,24 +48,19 @@ pub fn create_content_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/media/r0/download/<_server_name>/<_media_id>",
-        data = "<body>"
-    )
+    get("/_matrix/media/r0/download/<_>/<_>", data = "<body>")
 )]
-pub fn get_content_route(
+pub async fn get_content_route(
     db: State<'_, Database>,
-    body: Ruma<get_content::Request>,
-    _server_name: String,
-    _media_id: String,
+    body: Ruma<get_content::Request<'_>>,
 ) -> ConduitResult<get_content::Response> {
+    let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
+
     if let Some(FileMeta {
         filename,
         content_type,
         file,
-    }) = db
-        .media
-        .get(format!("mxc://{}/{}", body.server_name, body.media_id))?
+    }) = db.media.get(&mxc)?
     {
         Ok(get_content::Response {
             file,
@@ -71,6 +68,26 @@ pub fn get_content_route(
             content_disposition: filename.unwrap_or_default(), // TODO: Spec says this should be optional
         }
         .into())
+    } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
+        let get_content_response = server_server::send_request(
+            &db.globals,
+            body.server_name.clone(),
+            get_content::Request {
+                allow_remote: false,
+                server_name: &body.server_name,
+                media_id: &body.media_id,
+            },
+        )
+        .await?;
+
+        db.media.create(
+            mxc,
+            &Some(&get_content_response.content_disposition),
+            &get_content_response.content_type,
+            &get_content_response.file,
+        )?;
+
+        Ok(get_content_response.into())
     } else {
         Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
     }
@@ -78,21 +95,18 @@ pub fn get_content_route(
 
 #[cfg_attr(
     feature = "conduit_bin",
-    get(
-        "/_matrix/media/r0/thumbnail/<_server_name>/<_media_id>",
-        data = "<body>"
-    )
+    get("/_matrix/media/r0/thumbnail/<_>/<_>", data = "<body>")
 )]
-pub fn get_content_thumbnail_route(
+pub async fn get_content_thumbnail_route(
     db: State<'_, Database>,
-    body: Ruma<get_content_thumbnail::Request>,
-    _server_name: String,
-    _media_id: String,
+    body: Ruma<get_content_thumbnail::Request<'_>>,
 ) -> ConduitResult<get_content_thumbnail::Response> {
+    let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
+
     if let Some(FileMeta {
         content_type, file, ..
     }) = db.media.get_thumbnail(
-        format!("mxc://{}/{}", body.server_name, body.media_id),
+        mxc.clone(),
         body.width
             .try_into()
             .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
@@ -101,6 +115,31 @@ pub fn get_content_thumbnail_route(
             .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
     )? {
         Ok(get_content_thumbnail::Response { file, content_type }.into())
+    } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
+        let get_thumbnail_response = server_server::send_request(
+            &db.globals,
+            body.server_name.clone(),
+            get_content_thumbnail::Request {
+                allow_remote: false,
+                height: body.height,
+                width: body.width,
+                method: body.method,
+                server_name: &body.server_name,
+                media_id: &body.media_id,
+            },
+        )
+        .await?;
+
+        db.media.upload_thumbnail(
+            mxc,
+            &None,
+            &get_thumbnail_response.content_type,
+            body.width.try_into().expect("all UInts are valid u32s"),
+            body.height.try_into().expect("all UInts are valid u32s"),
+            &get_thumbnail_response.file,
+        )?;
+
+        Ok(get_thumbnail_response.into())
     } else {
         Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
     }
diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs
index 84c0ebd31d0d37da17306a79ff9f1f9fda58bc97..526e82f1abd58e745618eee95efb0a0104c095e0 100644
--- a/src/client_server/membership.rs
+++ b/src/client_server/membership.rs
@@ -1,26 +1,30 @@
 use super::State;
 use crate::{
-    client_server, pdu::PduBuilder, server_server, utils, ConduitResult, Database, Error, Ruma,
+    client_server,
+    pdu::{PduBuilder, PduEvent},
+    server_server, utils, ConduitResult, Database, Error, Result, Ruma,
 };
+use log::warn;
 use ruma::{
     api::{
         client::{
             error::ErrorKind,
-            r0::{
-                alias,
-                membership::{
-                    ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
-                    join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
-                    unban_user,
-                },
+            r0::membership::{
+                ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
+                join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
+                unban_user, IncomingThirdPartySigned,
             },
         },
         federation,
     },
+    events::pdu::Pdu,
     events::{room::member, EventType},
-    EventId, Raw, RoomId, RoomVersionId,
+    EventId, Raw, RoomId, RoomVersionId, ServerName, UserId,
+};
+use state_res::StateEvent;
+use std::{
+    collections::BTreeMap, collections::HashMap, collections::HashSet, convert::TryFrom, sync::Arc,
 };
-use std::{collections::BTreeMap, convert::TryFrom};
 
 #[cfg(feature = "conduit_bin")]
 use rocket::{get, post};
@@ -31,106 +35,16 @@
 )]
 pub async fn join_room_by_id_route(
     db: State<'_, Database>,
-    body: Ruma<join_room_by_id::IncomingRequest>,
+    body: Ruma<join_room_by_id::Request<'_>>,
 ) -> ConduitResult<join_room_by_id::Response> {
-    let sender_id = body.sender_id.as_ref().expect("user is authenticated");
-
-    // Ask a remote server if we don't have this room
-    if !db.rooms.exists(&body.room_id)? && body.room_id.server_name() != db.globals.server_name() {
-        let make_join_response = server_server::send_request(
-            &db,
-            body.room_id.server_name().to_string(),
-            federation::membership::create_join_event_template::v1::Request {
-                room_id: body.room_id.clone(),
-                user_id: sender_id.clone(),
-                ver: vec![RoomVersionId::Version5, RoomVersionId::Version6],
-            },
-        )
-        .await?;
-
-        let mut join_event_stub_value =
-            serde_json::from_str::<serde_json::Value>(make_join_response.event.json().get())
-                .map_err(|_| {
-                    Error::BadServerResponse("Invalid make_join event json received from server.")
-                })?;
-
-        let join_event_stub =
-            join_event_stub_value
-                .as_object_mut()
-                .ok_or(Error::BadServerResponse(
-                    "Invalid make join event object received from server.",
-                ))?;
-
-        join_event_stub.insert(
-            "origin".to_owned(),
-            db.globals.server_name().to_owned().to_string().into(),
-        );
-        join_event_stub.insert(
-            "origin_server_ts".to_owned(),
-            utils::millis_since_unix_epoch().into(),
-        );
-
-        // Generate event id
-        let event_id = EventId::try_from(&*format!(
-            "${}",
-            ruma::signatures::reference_hash(&join_event_stub_value)
-                .expect("ruma can calculate reference hashes")
-        ))
-        .expect("ruma's reference hashes are valid event ids");
-
-        // We don't leave the event id into the pdu because that's only allowed in v1 or v2 rooms
-        let join_event_stub = join_event_stub_value.as_object_mut().unwrap();
-        join_event_stub.remove("event_id");
-
-        ruma::signatures::hash_and_sign_event(
-            db.globals.server_name().as_str(),
-            db.globals.keypair(),
-            &mut join_event_stub_value,
-        )
-        .expect("event is valid, we just created it");
-
-        let send_join_response = server_server::send_request(
-            &db,
-            body.room_id.server_name().to_string(),
-            federation::membership::create_join_event::v2::Request {
-                room_id: body.room_id.clone(),
-                event_id,
-                pdu_stub: serde_json::from_value::<Raw<_>>(join_event_stub_value)
-                    .expect("Raw::from_value always works"),
-            },
-        )
-        .await?;
-
-        dbg!(send_join_response);
-        todo!("Take send_join_response and 'create' the room using that data");
-    }
-
-    let event = member::MemberEventContent {
-        membership: member::MembershipState::Join,
-        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(
-        PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
-            event_type: EventType::RoomMember,
-            content: serde_json::to_value(event).expect("event is valid, we just created it"),
-            unsigned: None,
-            state_key: Some(sender_id.to_string()),
-            redacts: None,
-        },
-        &db.globals,
-        &db.account_data,
-    )?;
-
-    Ok(join_room_by_id::Response {
-        room_id: body.room_id.clone(),
-    }
-    .into())
+    join_room_by_id_helper(
+        &db,
+        body.sender_id.as_ref(),
+        &body.room_id,
+        &[body.room_id.server_name().to_owned()],
+        body.third_party_signed.as_ref(),
+    )
+    .await
 }
 
 #[cfg_attr(
@@ -139,39 +53,28 @@ pub async fn join_room_by_id_route(
 )]
 pub async fn join_room_by_id_or_alias_route(
     db: State<'_, Database>,
-    db2: State<'_, Database>,
-    body: Ruma<join_room_by_id_or_alias::Request>,
+    body: Ruma<join_room_by_id_or_alias::Request<'_>>,
 ) -> ConduitResult<join_room_by_id_or_alias::Response> {
-    let room_id = match RoomId::try_from(body.room_id_or_alias.clone()) {
-        Ok(room_id) => room_id,
+    let (servers, room_id) = match RoomId::try_from(body.room_id_or_alias.clone()) {
+        Ok(room_id) => (vec![room_id.server_name().to_owned()], room_id),
         Err(room_alias) => {
-            client_server::get_alias_route(
-                db,
-                Ruma {
-                    body: alias::get_alias::IncomingRequest { room_alias },
-                    sender_id: body.sender_id.clone(),
-                    device_id: body.device_id.clone(),
-                    json_body: None,
-                },
-            )
-            .await?
-            .0
-            .room_id
-        }
-    };
+            let response = client_server::get_alias_helper(&db, &room_alias).await?;
 
-    let body = Ruma {
-        sender_id: body.sender_id.clone(),
-        device_id: body.device_id.clone(),
-        json_body: None,
-        body: join_room_by_id::IncomingRequest {
-            room_id,
-            third_party_signed: body.third_party_signed.clone(),
-        },
+            (response.0.servers, response.0.room_id)
+        }
     };
 
     Ok(join_room_by_id_or_alias::Response {
-        room_id: join_room_by_id_route(db2, body).await?.0.room_id,
+        room_id: join_room_by_id_helper(
+            &db,
+            body.sender_id.as_ref(),
+            &room_id,
+            &servers,
+            body.third_party_signed.as_ref(),
+        )
+        .await?
+        .0
+        .room_id,
     }
     .into())
 }
@@ -180,9 +83,9 @@ pub async fn join_room_by_id_or_alias_route(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/leave", data = "<body>")
 )]
-pub fn leave_room_route(
+pub async fn leave_room_route(
     db: State<'_, Database>,
-    body: Ruma<leave_room::IncomingRequest>,
+    body: Ruma<leave_room::Request<'_>>,
 ) -> ConduitResult<leave_room::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -205,38 +108,37 @@ pub fn leave_room_route(
 
     event.membership = member::MembershipState::Leave;
 
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(event).expect("event is valid, we just created it"),
             unsigned: None,
             state_key: Some(sender_id.to_string()),
             redacts: None,
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
-    Ok(leave_room::Response.into())
+    Ok(leave_room::Response::new().into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/invite", data = "<body>")
 )]
-pub fn invite_user_route(
+pub async fn invite_user_route(
     db: State<'_, Database>,
-    body: Ruma<invite_user::Request>,
+    body: Ruma<invite_user::Request<'_>>,
 ) -> 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(
+    if let invite_user::IncomingInvitationRecipient::UserId { user_id } = &body.recipient {
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: body.room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomMember,
                 content: serde_json::to_value(member::MemberEventContent {
                     membership: member::MembershipState::Invite,
@@ -250,7 +152,10 @@ pub fn invite_user_route(
                 state_key: Some(user_id.to_string()),
                 redacts: None,
             },
+            &sender_id,
+            &body.room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
 
@@ -264,9 +169,9 @@ pub fn invite_user_route(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/kick", data = "<body>")
 )]
-pub fn kick_user_route(
+pub async fn kick_user_route(
     db: State<'_, Database>,
-    body: Ruma<kick_user::Request>,
+    body: Ruma<kick_user::Request<'_>>,
 ) -> ConduitResult<kick_user::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -290,30 +195,31 @@ pub fn kick_user_route(
     event.membership = ruma::events::room::member::MembershipState::Leave;
     // TODO: reason
 
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(event).expect("event is valid, we just created it"),
             unsigned: None,
             state_key: Some(body.user_id.to_string()),
             redacts: None,
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
-    Ok(kick_user::Response.into())
+    Ok(kick_user::Response::new().into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/ban", data = "<body>")
 )]
-pub fn ban_user_route(
+pub async fn ban_user_route(
     db: State<'_, Database>,
-    body: Ruma<ban_user::Request>,
+    body: Ruma<ban_user::Request<'_>>,
 ) -> ConduitResult<ban_user::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -345,30 +251,31 @@ pub fn ban_user_route(
             },
         )?;
 
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(event).expect("event is valid, we just created it"),
             unsigned: None,
             state_key: Some(body.user_id.to_string()),
             redacts: None,
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
-    Ok(ban_user::Response.into())
+    Ok(ban_user::Response::new().into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_>/unban", data = "<body>")
 )]
-pub fn unban_user_route(
+pub async fn unban_user_route(
     db: State<'_, Database>,
-    body: Ruma<unban_user::Request>,
+    body: Ruma<unban_user::Request<'_>>,
 ) -> ConduitResult<unban_user::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -391,21 +298,22 @@ pub fn unban_user_route(
 
     event.membership = ruma::events::room::member::MembershipState::Leave;
 
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(event).expect("event is valid, we just created it"),
             unsigned: None,
             state_key: Some(body.user_id.to_string()),
             redacts: None,
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
-    Ok(unban_user::Response.into())
+    Ok(unban_user::Response::new().into())
 }
 
 #[cfg_attr(
@@ -414,13 +322,13 @@ pub fn unban_user_route(
 )]
 pub fn forget_room_route(
     db: State<'_, Database>,
-    body: Ruma<forget_room::Request>,
+    body: Ruma<forget_room::Request<'_>>,
 ) -> ConduitResult<forget_room::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
     db.rooms.forget(&body.room_id, &sender_id)?;
 
-    Ok(forget_room::Response.into())
+    Ok(forget_room::Response::new().into())
 }
 
 #[cfg_attr(
@@ -449,7 +357,7 @@ pub fn joined_rooms_route(
 )]
 pub fn get_member_events_route(
     db: State<'_, Database>,
-    body: Ruma<get_member_events::Request>,
+    body: Ruma<get_member_events::Request<'_>>,
 ) -> ConduitResult<get_member_events::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -477,7 +385,7 @@ pub fn get_member_events_route(
 )]
 pub fn joined_members_route(
     db: State<'_, Database>,
-    body: Ruma<joined_members::Request>,
+    body: Ruma<joined_members::Request<'_>>,
 ) -> ConduitResult<joined_members::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -508,3 +416,253 @@ pub fn joined_members_route(
 
     Ok(joined_members::Response { joined }.into())
 }
+
+async fn join_room_by_id_helper(
+    db: &Database,
+    sender_id: Option<&UserId>,
+    room_id: &RoomId,
+    servers: &[Box<ServerName>],
+    _third_party_signed: Option<&IncomingThirdPartySigned>,
+) -> ConduitResult<join_room_by_id::Response> {
+    let sender_id = sender_id.expect("user is authenticated");
+
+    // Ask a remote server if we don't have this room
+    if !db.rooms.exists(&room_id)? && room_id.server_name() != db.globals.server_name() {
+        let mut make_join_response_and_server = Err(Error::BadServerResponse(
+            "No server available to assist in joining.",
+        ));
+
+        for remote_server in servers {
+            let make_join_response = server_server::send_request(
+                &db.globals,
+                remote_server.clone(),
+                federation::membership::create_join_event_template::v1::Request {
+                    room_id,
+                    user_id: sender_id,
+                    ver: &[RoomVersionId::Version5, RoomVersionId::Version6],
+                },
+            )
+            .await;
+
+            make_join_response_and_server = make_join_response.map(|r| (r, remote_server));
+
+            if make_join_response_and_server.is_ok() {
+                break;
+            }
+        }
+
+        let (make_join_response, remote_server) = make_join_response_and_server?;
+
+        let mut join_event_stub_value =
+            serde_json::from_str::<serde_json::Value>(make_join_response.event.json().get())
+                .map_err(|_| {
+                    Error::BadServerResponse("Invalid make_join event json received from server.")
+                })?;
+
+        let join_event_stub =
+            join_event_stub_value
+                .as_object_mut()
+                .ok_or(Error::BadServerResponse(
+                    "Invalid make join event object received from server.",
+                ))?;
+
+        join_event_stub.insert(
+            "origin".to_owned(),
+            db.globals.server_name().to_owned().to_string().into(),
+        );
+        join_event_stub.insert(
+            "origin_server_ts".to_owned(),
+            utils::millis_since_unix_epoch().into(),
+        );
+
+        // Generate event id
+        let event_id = EventId::try_from(&*format!(
+            "${}",
+            ruma::signatures::reference_hash(&join_event_stub_value)
+                .expect("ruma can calculate reference hashes")
+        ))
+        .expect("ruma's reference hashes are valid event ids");
+
+        // We don't leave the event id into the pdu because that's only allowed in v1 or v2 rooms
+        let join_event_stub = join_event_stub_value.as_object_mut().unwrap();
+        join_event_stub.remove("event_id");
+
+        ruma::signatures::hash_and_sign_event(
+            db.globals.server_name().as_str(),
+            db.globals.keypair(),
+            &mut join_event_stub_value,
+        )
+        .expect("event is valid, we just created it");
+
+        let send_join_response = server_server::send_request(
+            &db.globals,
+            remote_server.clone(),
+            federation::membership::create_join_event::v2::Request {
+                room_id,
+                event_id: &event_id,
+                pdu_stub: serde_json::from_value(join_event_stub_value)
+                    .expect("we just created this event"),
+            },
+        )
+        .await?;
+
+        let add_event_id = |pdu: &Raw<Pdu>| {
+            let mut value = serde_json::from_str(pdu.json().get())
+                .expect("converting raw jsons to values always works");
+            let event_id = EventId::try_from(&*format!(
+                "${}",
+                ruma::signatures::reference_hash(&value)
+                    .expect("ruma can calculate reference hashes")
+            ))
+            .expect("ruma's reference hashes are valid event ids");
+
+            value
+                .as_object_mut()
+                .ok_or_else(|| Error::BadServerResponse("PDU is not an object."))?
+                .insert("event_id".to_owned(), event_id.to_string().into());
+
+            Ok((event_id, value))
+        };
+
+        let room_state = send_join_response.room_state.state.iter().map(add_event_id);
+
+        let state_events = room_state
+            .clone()
+            .map(|pdu: Result<(EventId, serde_json::Value)>| Ok(pdu?.0))
+            .collect::<Result<HashSet<EventId>>>()?;
+
+        let auth_chain = send_join_response
+            .room_state
+            .auth_chain
+            .iter()
+            .map(add_event_id);
+
+        let mut event_map = room_state
+            .chain(auth_chain)
+            .map(|r| {
+                let (event_id, value) = r?;
+                serde_json::from_value::<StateEvent>(value)
+                    .map(|ev| (event_id, Arc::new(ev)))
+                    .map_err(|e| {
+                        warn!("{}", e);
+                        Error::BadServerResponse("Invalid PDU bytes in send_join response.")
+                    })
+            })
+            .collect::<Result<BTreeMap<EventId, Arc<StateEvent>>>>()?;
+
+        let control_events = event_map
+            .values()
+            .filter(|pdu| pdu.is_power_event())
+            .map(|pdu| pdu.event_id().clone())
+            .collect::<Vec<_>>();
+
+        // These events are not guaranteed to be sorted but they are resolved according to spec
+        // we auth them anyways to weed out faulty/malicious server. The following is basically the
+        // full state resolution algorithm.
+        let event_ids = event_map.keys().cloned().collect::<Vec<_>>();
+
+        let sorted_control_events = state_res::StateResolution::reverse_topological_power_sort(
+            &room_id,
+            &control_events,
+            &mut event_map,
+            &db.rooms,
+            &event_ids,
+        );
+
+        // Auth check each event against the "partial" state created by the preceding events
+        let resolved_control_events = state_res::StateResolution::iterative_auth_check(
+            room_id,
+            &RoomVersionId::Version6,
+            &sorted_control_events,
+            &BTreeMap::new(), // We have no "clean/resolved" events to add (these extend the `resolved_control_events`)
+            &mut event_map,
+            &db.rooms,
+        )
+        .expect("iterative auth check failed on resolved events");
+
+        // This removes the control events that failed auth, leaving the resolved
+        // to be mainline sorted
+        let events_to_sort = event_map
+            .keys()
+            .filter(|id| {
+                !sorted_control_events.contains(id)
+                    || resolved_control_events.values().any(|rid| *id == rid)
+            })
+            .cloned()
+            .collect::<Vec<_>>();
+
+        let power_level = resolved_control_events.get(&(EventType::RoomPowerLevels, "".into()));
+        // Sort the remaining non control events
+        let sorted_event_ids = state_res::StateResolution::mainline_sort(
+            room_id,
+            &events_to_sort,
+            power_level,
+            &mut event_map,
+            &db.rooms,
+        );
+
+        let resolved_events = state_res::StateResolution::iterative_auth_check(
+            room_id,
+            &RoomVersionId::Version6,
+            &sorted_event_ids,
+            &resolved_control_events,
+            &mut event_map,
+            &db.rooms,
+        )
+        .expect("iterative auth check failed on resolved events");
+
+        let mut state = HashMap::new();
+
+        // filter the events that failed the auth check keeping the remaining events
+        // sorted correctly
+        for ev_id in sorted_event_ids
+            .iter()
+            .filter(|id| resolved_events.values().any(|rid| rid == *id))
+        {
+            // this is a `state_res::StateEvent` that holds a `ruma::Pdu`
+            let pdu = event_map
+                .get(ev_id)
+                .expect("Found event_id in sorted events that is not in resolved state");
+
+            // We do not rebuild the PDU in this case only insert to DB
+            let pdu_id = db.rooms.append_pdu(
+                &PduEvent::from(&**pdu),
+                &serde_json::to_value(&**pdu).expect("PDU is valid value"),
+                &db.globals,
+                &db.account_data,
+                &db.sending,
+            )?;
+
+            if state_events.contains(ev_id) {
+                state.insert((pdu.kind(), pdu.state_key()), pdu_id);
+            }
+        }
+
+        db.rooms.force_state(room_id, state)?;
+    } else {
+        let event = member::MemberEventContent {
+            membership: member::MembershipState::Join,
+            displayname: db.users.displayname(&sender_id)?,
+            avatar_url: db.users.avatar_url(&sender_id)?,
+            is_direct: None,
+            third_party_invite: None,
+        };
+
+        db.rooms.build_and_append_pdu(
+            PduBuilder {
+                event_type: EventType::RoomMember,
+                content: serde_json::to_value(event).expect("event is valid, we just created it"),
+                unsigned: None,
+                state_key: Some(sender_id.to_string()),
+                redacts: None,
+            },
+            &sender_id,
+            &room_id,
+            &db.globals,
+            &db.sending,
+            &db.account_data,
+        )?;
+    }
+
+    Ok(join_room_by_id::Response::new(room_id.clone()).into())
+}
diff --git a/src/client_server/message.rs b/src/client_server/message.rs
index 844f44d10dcffb816ffcef4706228ed2b0707cd6..c32bd687adce08b48400c48838dbe08e578bbfda 100644
--- a/src/client_server/message.rs
+++ b/src/client_server/message.rs
@@ -5,6 +5,7 @@
         error::ErrorKind,
         r0::message::{get_message_events, send_message_event},
     },
+    events::EventContent,
     EventId,
 };
 use std::convert::{TryFrom, TryInto};
@@ -16,9 +17,9 @@
     feature = "conduit_bin",
     put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "<body>")
 )]
-pub fn send_message_event_route(
+pub async fn send_message_event_route(
     db: State<'_, Database>,
-    body: Ruma<send_message_event::IncomingRequest>,
+    body: Ruma<send_message_event::Request<'_>>,
 ) -> ConduitResult<send_message_event::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -48,11 +49,9 @@ pub fn send_message_event_route(
     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(
+    let event_id = db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
-            event_type: body.event_type.clone(),
+            event_type: body.content.event_type().into(),
             content: serde_json::from_str(
                 body.json_body
                     .as_ref()
@@ -64,13 +63,17 @@ pub fn send_message_event_route(
             state_key: None,
             redacts: None,
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     db.transaction_ids
         .add_txnid(sender_id, device_id, &body.txn_id, event_id.as_bytes())?;
-    Ok(send_message_event::Response { event_id }.into())
+
+    Ok(send_message_event::Response::new(event_id).into())
 }
 
 #[cfg_attr(
@@ -79,7 +82,7 @@ pub fn send_message_event_route(
 )]
 pub fn get_message_events_route(
     db: State<'_, Database>,
-    body: Ruma<get_message_events::Request>,
+    body: Ruma<get_message_events::Request<'_>>,
 ) -> ConduitResult<get_message_events::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -111,6 +114,12 @@ pub fn get_message_events_route(
                 .pdus_after(&sender_id, &body.room_id, from)
                 .take(limit)
                 .filter_map(|r| r.ok()) // Filter out buggy events
+                .filter_map(|(pdu_id, pdu)| {
+                    db.rooms
+                        .pdu_count(&pdu_id)
+                        .map(|pdu_count| (pdu_count, pdu))
+                        .ok()
+                })
                 .take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
                 .collect::<Vec<_>>();
 
@@ -121,13 +130,13 @@ pub fn get_message_events_route(
                 .map(|(_, pdu)| pdu.to_room_event())
                 .collect::<Vec<_>>();
 
-            Ok(get_message_events::Response {
-                start: Some(body.from.clone()),
-                end: end_token,
-                chunk: events_after,
-                state: Vec::new(),
-            }
-            .into())
+            let mut resp = get_message_events::Response::new();
+            resp.start = Some(body.from.to_owned());
+            resp.end = end_token;
+            resp.chunk = events_after;
+            resp.state = Vec::new();
+
+            Ok(resp.into())
         }
         get_message_events::Direction::Backward => {
             let events_before = db
@@ -135,6 +144,12 @@ pub fn get_message_events_route(
                 .pdus_until(&sender_id, &body.room_id, from)
                 .take(limit)
                 .filter_map(|r| r.ok()) // Filter out buggy events
+                .filter_map(|(pdu_id, pdu)| {
+                    db.rooms
+                        .pdu_count(&pdu_id)
+                        .map(|pdu_count| (pdu_count, pdu))
+                        .ok()
+                })
                 .take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
                 .collect::<Vec<_>>();
 
@@ -145,13 +160,13 @@ pub fn get_message_events_route(
                 .map(|(_, pdu)| pdu.to_room_event())
                 .collect::<Vec<_>>();
 
-            Ok(get_message_events::Response {
-                start: Some(body.from.clone()),
-                end: start_token,
-                chunk: events_before,
-                state: Vec::new(),
-            }
-            .into())
+            let mut resp = get_message_events::Response::new();
+            resp.start = Some(body.from.to_owned());
+            resp.end = start_token;
+            resp.chunk = events_before;
+            resp.state = Vec::new();
+
+            Ok(resp.into())
         }
     }
 }
diff --git a/src/client_server/presence.rs b/src/client_server/presence.rs
index 0b6a51f4e7bd812d012ff83517063a00fcdf9b1d..d105eb6ab3168aee5fee9ed9c10d24320c77cc43 100644
--- a/src/client_server/presence.rs
+++ b/src/client_server/presence.rs
@@ -12,7 +12,7 @@
 )]
 pub fn set_presence_route(
     db: State<'_, Database>,
-    body: Ruma<set_presence::Request>,
+    body: Ruma<set_presence::Request<'_>>,
 ) -> ConduitResult<set_presence::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
diff --git a/src/client_server/profile.rs b/src/client_server/profile.rs
index ebcc7eb8f32b80a0359b852364a283bee9ae75fd..9c6bd512eac941ab3bccffbecb195dc3fe918639 100644
--- a/src/client_server/profile.rs
+++ b/src/client_server/profile.rs
@@ -19,9 +19,9 @@
     feature = "conduit_bin",
     put("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
 )]
-pub fn set_displayname_route(
+pub async fn set_displayname_route(
     db: State<'_, Database>,
-    body: Ruma<set_display_name::Request>,
+    body: Ruma<set_display_name::Request<'_>>,
 ) -> ConduitResult<set_display_name::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -31,10 +31,8 @@ pub fn set_displayname_route(
     // 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(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomMember,
                 content: serde_json::to_value(ruma::events::room::member::MemberEventContent {
                     displayname: body.displayname.clone(),
@@ -62,7 +60,10 @@ pub fn set_displayname_route(
                 state_key: Some(sender_id.to_string()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
 
@@ -98,7 +99,7 @@ pub fn set_displayname_route(
 )]
 pub fn get_displayname_route(
     db: State<'_, Database>,
-    body: Ruma<get_display_name::Request>,
+    body: Ruma<get_display_name::Request<'_>>,
 ) -> ConduitResult<get_display_name::Response> {
     Ok(get_display_name::Response {
         displayname: db.users.displayname(&body.user_id)?,
@@ -110,34 +111,20 @@ pub fn get_displayname_route(
     feature = "conduit_bin",
     put("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
 )]
-pub fn set_avatar_url_route(
+pub async fn set_avatar_url_route(
     db: State<'_, Database>,
-    body: Ruma<set_avatar_url::Request>,
+    body: Ruma<set_avatar_url::Request<'_>>,
 ) -> ConduitResult<set_avatar_url::Response> {
     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://") {
-            return Err(Error::BadRequest(
-                ErrorKind::InvalidParam,
-                "avatar_url has to start with mxc://.",
-            ));
-        }
-
-        // TODO in the future when we can handle media uploads make sure that this url is our own server
-        // 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())?;
 
     // 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(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomMember,
                 content: serde_json::to_value(ruma::events::room::member::MemberEventContent {
                     avatar_url: body.avatar_url.clone(),
@@ -165,7 +152,10 @@ pub fn set_avatar_url_route(
                 state_key: Some(sender_id.to_string()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
 
@@ -201,7 +191,7 @@ pub fn set_avatar_url_route(
 )]
 pub fn get_avatar_url_route(
     db: State<'_, Database>,
-    body: Ruma<get_avatar_url::Request>,
+    body: Ruma<get_avatar_url::Request<'_>>,
 ) -> ConduitResult<get_avatar_url::Response> {
     Ok(get_avatar_url::Response {
         avatar_url: db.users.avatar_url(&body.user_id)?,
@@ -215,7 +205,7 @@ pub fn get_avatar_url_route(
 )]
 pub fn get_profile_route(
     db: State<'_, Database>,
-    body: Ruma<get_profile::Request>,
+    body: Ruma<get_profile::Request<'_>>,
 ) -> ConduitResult<get_profile::Response> {
     if !db.users.exists(&body.user_id)? {
         // Return 404 if this user doesn't exist
diff --git a/src/client_server/read_marker.rs b/src/client_server/read_marker.rs
index 1b8bd8e4e8158d979548d955f880349352cc2523..34d1ccc0a3fce32b889fd77a6c9a0c4d08874677 100644
--- a/src/client_server/read_marker.rs
+++ b/src/client_server/read_marker.rs
@@ -15,7 +15,7 @@
 )]
 pub fn set_read_marker_route(
     db: State<'_, Database>,
-    body: Ruma<set_read_marker::Request>,
+    body: Ruma<set_read_marker::Request<'_>>,
 ) -> ConduitResult<set_read_marker::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -53,7 +53,7 @@ pub fn set_read_marker_route(
         );
         let mut receipt_content = BTreeMap::new();
         receipt_content.insert(
-            event.clone(),
+            event.to_owned(),
             ruma::events::receipt::Receipts {
                 read: Some(user_receipts),
             },
diff --git a/src/client_server/redact.rs b/src/client_server/redact.rs
index fc65c23f584fa1427c4874d7dd7cc972b71171f2..b13cd802b1e2021c09bee09fccdea0b1ac645ae7 100644
--- a/src/client_server/redact.rs
+++ b/src/client_server/redact.rs
@@ -12,16 +12,14 @@
     feature = "conduit_bin",
     put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "<body>")
 )]
-pub fn redact_event_route(
+pub async fn redact_event_route(
     db: State<'_, Database>,
-    body: Ruma<redact_event::Request>,
+    body: Ruma<redact_event::Request<'_>>,
 ) -> ConduitResult<redact_event::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    let event_id = db.rooms.append_pdu(
+    let event_id = db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomRedaction,
             content: serde_json::to_value(redaction::RedactionEventContent {
                 reason: body.reason.clone(),
@@ -31,7 +29,10 @@ pub fn redact_event_route(
             state_key: None,
             redacts: Some(body.event_id.clone()),
         },
+        &sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
diff --git a/src/client_server/room.rs b/src/client_server/room.rs
index 1b4387337a69399862c76bf1568175be39dfa6d3..744d9490dbc49639f1b89f632f6608cab1c452f6 100644
--- a/src/client_server/room.rs
+++ b/src/client_server/room.rs
@@ -20,9 +20,9 @@
     feature = "conduit_bin",
     post("/_matrix/client/r0/createRoom", data = "<body>")
 )]
-pub fn create_room_route(
+pub async fn create_room_route(
     db: State<'_, Database>,
-    body: Ruma<create_room::Request>,
+    body: Ruma<create_room::Request<'_>>,
 ) -> ConduitResult<create_room::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -48,39 +48,35 @@ pub fn create_room_route(
         })?;
 
     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
-        .as_ref()
-        .and_then(|c| c.predecessor.clone());
+    content.federate = body.creation_content.federate;
+    content.predecessor = body.creation_content.predecessor.clone();
     content.room_version = RoomVersionId::Version6;
 
     // 1. The room create event
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomCreate,
             content: serde_json::to_value(content).expect("event is valid, we just created it"),
             unsigned: None,
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // 2. Let the room creator join
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(member::MemberEventContent {
                 membership: member::MembershipState::Join,
                 displayname: db.users.displayname(&sender_id)?,
                 avatar_url: db.users.avatar_url(&sender_id)?,
-                is_direct: body.is_direct,
+                is_direct: Some(body.is_direct),
                 third_party_invite: None,
             })
             .expect("event is valid, we just created it"),
@@ -88,7 +84,10 @@ pub fn create_room_route(
             state_key: Some(sender_id.to_string()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
@@ -120,34 +119,32 @@ pub fn create_room_route(
         })
         .expect("event is valid, we just created it")
     };
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomPowerLevels,
             content: power_levels_content,
             unsigned: None,
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // 4. Events set by preset
 
     // Figure out preset. We need it for preset specific events
-    let visibility = body.visibility.unwrap_or(room::Visibility::Private);
-    let preset = body.preset.unwrap_or_else(|| match visibility {
+    let preset = body.preset.unwrap_or_else(|| match body.visibility {
         room::Visibility::Private => create_room::RoomPreset::PrivateChat,
         room::Visibility::Public => create_room::RoomPreset::PublicChat,
     });
 
     // 4.1 Join Rules
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomJoinRules,
             content: match preset {
                 create_room::RoomPreset::PublicChat => serde_json::to_value(
@@ -164,15 +161,16 @@ pub fn create_room_route(
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // 4.2 History Visibility
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomHistoryVisibility,
             content: serde_json::to_value(history_visibility::HistoryVisibilityEventContent::new(
                 history_visibility::HistoryVisibility::Shared,
@@ -182,15 +180,16 @@ pub fn create_room_route(
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // 4.3 Guest Access
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomGuestAccess,
             content: match preset {
                 create_room::RoomPreset::PublicChat => {
@@ -208,45 +207,39 @@ pub fn create_room_route(
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        &sender_id,
+        &room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // 5. Events listed in initial_state
-    for create_room::InitialStateEvent {
-        event_type,
-        state_key,
-        content,
-    } in &body.initial_state
-    {
+    for event in &body.initial_state {
+        let pdu_builder = serde_json::from_str::<PduBuilder>(
+            &serde_json::to_string(&event).expect("AnyInitialStateEvent::to_string always works"),
+        )
+        .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event."))?;
+
         // Silently skip encryption events if they are not allowed
-        if event_type == &EventType::RoomEncryption && db.globals.encryption_disabled() {
+        if pdu_builder.event_type == EventType::RoomEncryption && db.globals.encryption_disabled() {
             continue;
         }
 
-        db.rooms.append_pdu(
-            PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
-                event_type: event_type.clone(),
-                content: serde_json::from_str(content.get()).map_err(|_| {
-                    Error::BadRequest(ErrorKind::BadJson, "Invalid initial_state content.")
-                })?,
-                unsigned: None,
-                state_key: state_key.clone(),
-                redacts: None,
-            },
+        db.rooms.build_and_append_pdu(
+            pdu_builder,
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
 
     // 6. Events implied by name and topic
     if let Some(name) = &body.name {
-        db.rooms.append_pdu(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomName,
                 content: serde_json::to_value(
                     name::NameEventContent::new(name.clone()).map_err(|_| {
@@ -258,16 +251,17 @@ pub fn create_room_route(
                 state_key: Some("".to_owned()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
 
     if let Some(topic) = &body.topic {
-        db.rooms.append_pdu(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomTopic,
                 content: serde_json::to_value(topic::TopicEventContent {
                     topic: topic.clone(),
@@ -277,23 +271,24 @@ pub fn create_room_route(
                 state_key: Some("".to_owned()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
 
     // 7. Events implied by invite (and TODO: invite_3pid)
     for user in &body.invite {
-        db.rooms.append_pdu(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: room_id.clone(),
-                sender: sender_id.clone(),
                 event_type: EventType::RoomMember,
                 content: serde_json::to_value(member::MemberEventContent {
                     membership: member::MembershipState::Invite,
                     displayname: db.users.displayname(&user)?,
                     avatar_url: db.users.avatar_url(&user)?,
-                    is_direct: body.is_direct,
+                    is_direct: Some(body.is_direct),
                     third_party_invite: None,
                 })
                 .expect("event is valid, we just created it"),
@@ -301,7 +296,10 @@ pub fn create_room_route(
                 state_key: Some(user.to_string()),
                 redacts: None,
             },
+            &sender_id,
+            &room_id,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
@@ -311,11 +309,11 @@ pub fn create_room_route(
         db.rooms.set_alias(&alias, Some(&room_id), &db.globals)?;
     }
 
-    if let Some(room::Visibility::Public) = body.visibility {
+    if body.visibility == room::Visibility::Public {
         db.rooms.set_public(&room_id, true)?;
     }
 
-    Ok(create_room::Response { room_id }.into())
+    Ok(create_room::Response::new(room_id).into())
 }
 
 #[cfg_attr(
@@ -324,7 +322,7 @@ pub fn create_room_route(
 )]
 pub fn get_room_event_route(
     db: State<'_, Database>,
-    body: Ruma<get_room_event::Request>,
+    body: Ruma<get_room_event::Request<'_>>,
 ) -> ConduitResult<get_room_event::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -349,19 +347,15 @@ pub fn get_room_event_route(
     feature = "conduit_bin",
     post("/_matrix/client/r0/rooms/<_room_id>/upgrade", data = "<body>")
 )]
-pub fn upgrade_room_route(
+pub async fn upgrade_room_route(
     db: State<'_, Database>,
-    body: Ruma<upgrade_room::Request>,
+    body: Ruma<upgrade_room::Request<'_>>,
     _room_id: String,
 ) -> ConduitResult<upgrade_room::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    // Validate the room version requested
-    let new_version =
-        RoomVersionId::try_from(body.new_version.clone()).expect("invalid room version id");
-
     if !matches!(
-        new_version,
+        body.new_version,
         RoomVersionId::Version5 | RoomVersionId::Version6
     ) {
         return Err(Error::BadRequest(
@@ -375,10 +369,8 @@ pub fn upgrade_room_route(
 
     // Send a m.room.tombstone event to the old room to indicate that it is not intended to be used any further
     // Fail if the sender does not have the required permissions
-    let tombstone_event_id = db.rooms.append_pdu(
+    let tombstone_event_id = db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomTombstone,
             content: serde_json::to_value(ruma::events::room::tombstone::TombstoneEventContent {
                 body: "This room has been replaced".to_string(),
@@ -389,7 +381,10 @@ pub fn upgrade_room_route(
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        sender_id,
+        &body.room_id,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
@@ -415,13 +410,11 @@ pub fn upgrade_room_route(
     let mut create_event_content =
         ruma::events::room::create::CreateEventContent::new(sender_id.clone());
     create_event_content.federate = federate;
-    create_event_content.room_version = new_version;
+    create_event_content.room_version = body.new_version.clone();
     create_event_content.predecessor = predecessor;
 
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: replacement_room.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomCreate,
             content: serde_json::to_value(create_event_content)
                 .expect("event is valid, we just created it"),
@@ -429,15 +422,16 @@ pub fn upgrade_room_route(
             state_key: Some("".to_owned()),
             redacts: None,
         },
+        sender_id,
+        &replacement_room,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
     // Join the new room
-    db.rooms.append_pdu(
+    db.rooms.build_and_append_pdu(
         PduBuilder {
-            room_id: replacement_room.clone(),
-            sender: sender_id.clone(),
             event_type: EventType::RoomMember,
             content: serde_json::to_value(member::MemberEventContent {
                 membership: member::MembershipState::Join,
@@ -451,7 +445,10 @@ pub fn upgrade_room_route(
             state_key: Some(sender_id.to_string()),
             redacts: None,
         },
+        sender_id,
+        &replacement_room,
         &db.globals,
+        &db.sending,
         &db.account_data,
     )?;
 
@@ -475,17 +472,18 @@ pub fn upgrade_room_route(
             None => continue, // Skipping missing events.
         };
 
-        db.rooms.append_pdu(
+        db.rooms.build_and_append_pdu(
             PduBuilder {
-                room_id: replacement_room.clone(),
-                sender: sender_id.clone(),
                 event_type,
                 content: event_content,
                 unsigned: None,
                 state_key: Some("".to_owned()),
                 redacts: None,
             },
+            sender_id,
+            &replacement_room,
             &db.globals,
+            &db.sending,
             &db.account_data,
         )?;
     }
@@ -517,22 +515,21 @@ pub fn upgrade_room_route(
     power_levels_event_content.invite = new_level;
 
     // Modify the power levels in the old room to prevent sending of events and inviting new users
-    db.rooms
-        .append_pdu(
-            PduBuilder {
-                room_id: body.room_id.clone(),
-                sender: sender_id.clone(),
-                event_type: EventType::RoomPowerLevels,
-                content: serde_json::to_value(power_levels_event_content)
-                    .expect("event is valid, we just created it"),
-                unsigned: None,
-                state_key: Some("".to_owned()),
-                redacts: None,
-            },
-            &db.globals,
-            &db.account_data,
-        )
-        .ok();
+    let _ = db.rooms.build_and_append_pdu(
+        PduBuilder {
+            event_type: EventType::RoomPowerLevels,
+            content: serde_json::to_value(power_levels_event_content)
+                .expect("event is valid, we just created it"),
+            unsigned: None,
+            state_key: Some("".to_owned()),
+            redacts: None,
+        },
+        sender_id,
+        &body.room_id,
+        &db.globals,
+        &db.sending,
+        &db.account_data,
+    )?;
 
     // Return the replacement room id
     Ok(upgrade_room::Response { replacement_room }.into())
diff --git a/src/client_server/search.rs b/src/client_server/search.rs
index dec1ec9f644f3ee2dadf8f871396db1380b988f1..3b03e7ace089a056cb41118dc01c8898c305bfed 100644
--- a/src/client_server/search.rs
+++ b/src/client_server/search.rs
@@ -1,11 +1,10 @@
 use super::State;
 use crate::{ConduitResult, Database, Error, Ruma};
-use js_int::uint;
 use ruma::api::client::{error::ErrorKind, r0::search::search_events};
 
 #[cfg(feature = "conduit_bin")]
 use rocket::post;
-use search_events::{ResultCategories, ResultRoomEvents, SearchResult};
+use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult};
 use std::collections::BTreeMap;
 
 #[cfg_attr(
@@ -14,7 +13,7 @@
 )]
 pub fn search_events_route(
     db: State<'_, Database>,
-    body: Ruma<search_events::Request>,
+    body: Ruma<search_events::Request<'_>>,
 ) -> ConduitResult<search_events::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -51,7 +50,13 @@ pub fn search_events_route(
         .0
         .map(|result| {
             Ok::<_, Error>(SearchResult {
-                context: None,
+                context: EventContextResult {
+                    end: None,
+                    events_after: Vec::new(),
+                    events_before: Vec::new(),
+                    profile_info: BTreeMap::new(),
+                    start: None,
+                },
                 rank: None,
                 result: db
                     .rooms
@@ -70,17 +75,15 @@ pub fn search_events_route(
         Some((skip + limit).to_string())
     };
 
-    Ok(search_events::Response {
-        search_categories: ResultCategories {
-            room_events: Some(ResultRoomEvents {
-                count: uint!(0),         // TODO
-                groups: BTreeMap::new(), // TODO
-                next_batch,
-                results,
-                state: BTreeMap::new(), // TODO
-                highlights: search.1,
-            }),
+    Ok(search_events::Response::new(ResultCategories {
+        room_events: ResultRoomEvents {
+            count: None,             // TODO? maybe not
+            groups: BTreeMap::new(), // TODO
+            next_batch,
+            results,
+            state: BTreeMap::new(), // TODO
+            highlights: search.1,
         },
-    }
+    })
     .into())
 }
diff --git a/src/client_server/session.rs b/src/client_server/session.rs
index 401105882b0d8ae18307a0cbad836d072fa97f57..9cd051ce1e85b2181b336b531828230a5aa3a395 100644
--- a/src/client_server/session.rs
+++ b/src/client_server/session.rs
@@ -1,5 +1,4 @@
-use super::State;
-use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
+use super::{State, DEVICE_ID_LENGTH, TOKEN_LENGTH};
 use crate::{utils, ConduitResult, Database, Error, Ruma};
 use ruma::{
     api::client::{
@@ -18,10 +17,7 @@
 /// when logging in.
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))]
 pub fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
-    Ok(get_login_types::Response {
-        flows: vec![get_login_types::LoginType::Password],
-    }
-    .into())
+    Ok(get_login_types::Response::new(vec![get_login_types::LoginType::Password]).into())
 }
 
 /// # `POST /_matrix/client/r0/login`
@@ -40,15 +36,15 @@ pub fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
 )]
 pub fn login_route(
     db: State<'_, Database>,
-    body: Ruma<login::Request>,
+    body: Ruma<login::Request<'_>>,
 ) -> ConduitResult<login::Response> {
     // Validate login method
     let user_id =
         // TODO: Other login methods
-        if let (login::UserInfo::MatrixId(username), login::LoginInfo::Password { password }) =
-            (body.user.clone(), body.login_info.clone())
+        if let (login::IncomingUserInfo::MatrixId(username), login::IncomingLoginInfo::Password { password }) =
+            (&body.user, &body.login_info)
         {
-            let user_id = UserId::parse_with_server_name(username, db.globals.server_name())
+            let user_id = UserId::parse_with_server_name(username.to_string(), db.globals.server_name())
                 .map_err(|_| Error::BadRequest(
                     ErrorKind::InvalidUsername,
                     "Username is invalid."
@@ -126,7 +122,7 @@ pub fn logout_route(
 
     db.users.remove_device(&sender_id, device_id)?;
 
-    Ok(logout::Response.into())
+    Ok(logout::Response::new().into())
 }
 
 /// # `POST /_matrix/client/r0/logout/all`
@@ -154,5 +150,5 @@ pub fn logout_all_route(
         }
     }
 
-    Ok(logout_all::Response.into())
+    Ok(logout_all::Response::new().into())
 }
diff --git a/src/client_server/state.rs b/src/client_server/state.rs
index 60b3e9f118e87cf7335cea5836ed0fa2253e65dd..1e13b42febe5ea783afb0b48ebc0071ecfe12085 100644
--- a/src/client_server/state.rs
+++ b/src/client_server/state.rs
@@ -1,5 +1,5 @@
 use super::State;
-use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
+use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Result, Ruma};
 use ruma::{
     api::client::{
         error::ErrorKind,
@@ -8,8 +8,8 @@
             send_state_event_for_empty_key, send_state_event_for_key,
         },
     },
-    events::{room::canonical_alias, EventType},
-    Raw,
+    events::{AnyStateEventContent, EventContent},
+    EventId, RoomId, UserId,
 };
 
 #[cfg(feature = "conduit_bin")]
@@ -19,9 +19,9 @@
     feature = "conduit_bin",
     put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
 )]
-pub fn send_state_event_for_key_route(
+pub async fn send_state_event_for_key_route(
     db: State<'_, Database>,
-    body: Ruma<send_state_event_for_key::IncomingRequest>,
+    body: Ruma<send_state_event_for_key::Request<'_>>,
 ) -> ConduitResult<send_state_event_for_key::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -33,93 +33,57 @@ pub fn send_state_event_for_key_route(
     )
     .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?;
 
-    if body.event_type == EventType::RoomCanonicalAlias {
-        let canonical_alias = serde_json::from_value::<
-            Raw<canonical_alias::CanonicalAliasEventContent>,
-        >(content.clone())
-        .expect("from_value::<Raw<..>> can never fail")
-        .deserialize()
-        .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid canonical alias."))?;
-
-        let mut aliases = canonical_alias.alt_aliases;
-
-        if let Some(alias) = canonical_alias.alias {
-            aliases.push(alias);
-        }
-
-        for alias in aliases {
-            if alias.server_name() != db.globals.server_name()
-                || db
-                    .rooms
-                    .id_from_alias(&alias)?
-                    .filter(|room| room == &body.room_id) // Make sure it's the right room
-                    .is_none()
-            {
-                return Err(Error::BadRequest(
-                    ErrorKind::Forbidden,
-                    "You are only allowed to send canonical_alias \
-                    events when it's aliases already exists",
-                ));
-            }
-        }
-    }
-
-    let event_id = db.rooms.append_pdu(
-        PduBuilder {
-            room_id: body.room_id.clone(),
-            sender: sender_id.clone(),
-            event_type: body.event_type.clone(),
+    Ok(send_state_event_for_key::Response::new(
+        send_state_event_for_key_helper(
+            &db,
+            sender_id,
+            &body.content,
             content,
-            unsigned: None,
-            state_key: Some(body.state_key.clone()),
-            redacts: None,
-        },
-        &db.globals,
-        &db.account_data,
-    )?;
-
-    Ok(send_state_event_for_key::Response { event_id }.into())
+            &body.room_id,
+            Some(body.state_key.to_owned()),
+        )
+        .await?,
+    )
+    .into())
 }
 
 #[cfg_attr(
     feature = "conduit_bin",
     put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
 )]
-pub fn send_state_event_for_empty_key_route(
+pub async fn send_state_event_for_empty_key_route(
     db: State<'_, Database>,
-    body: Ruma<send_state_event_for_empty_key::IncomingRequest>,
+    body: Ruma<send_state_event_for_empty_key::Request<'_>>,
 ) -> ConduitResult<send_state_event_for_empty_key::Response> {
     // This just calls send_state_event_for_key_route
     let Ruma {
-        body:
-            send_state_event_for_empty_key::IncomingRequest {
-                room_id,
-                event_type,
-                data,
-            },
+        body,
         sender_id,
-        device_id,
+        device_id: _,
         json_body,
     } = body;
 
-    Ok(send_state_event_for_empty_key::Response {
-        event_id: send_state_event_for_key_route(
-            db,
-            Ruma {
-                body: send_state_event_for_key::IncomingRequest {
-                    room_id,
-                    event_type,
-                    data,
-                    state_key: "".to_owned(),
-                },
-                sender_id,
-                device_id,
-                json_body,
-            },
-        )?
-        .0
-        .event_id,
-    }
+    let json = serde_json::from_str::<serde_json::Value>(
+        json_body
+            .as_ref()
+            .ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?
+            .get(),
+    )
+    .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?;
+
+    Ok(send_state_event_for_empty_key::Response::new(
+        send_state_event_for_key_helper(
+            &db,
+            sender_id
+                .as_ref()
+                .expect("no user for send state empty key rout"),
+            &body.content,
+            json,
+            &body.room_id,
+            Some("".into()),
+        )
+        .await?,
+    )
     .into())
 }
 
@@ -214,3 +178,55 @@ pub fn get_state_events_for_empty_key_route(
     }
     .into())
 }
+
+pub async fn send_state_event_for_key_helper(
+    db: &Database,
+    sender: &UserId,
+    content: &AnyStateEventContent,
+    json: serde_json::Value,
+    room_id: &RoomId,
+    state_key: Option<String>,
+) -> Result<EventId> {
+    let sender_id = sender;
+
+    if let AnyStateEventContent::RoomCanonicalAlias(canonical_alias) = content {
+        let mut aliases = canonical_alias.alt_aliases.clone();
+
+        if let Some(alias) = canonical_alias.alias.clone() {
+            aliases.push(alias);
+        }
+
+        for alias in aliases {
+            if alias.server_name() != db.globals.server_name()
+                || db
+                    .rooms
+                    .id_from_alias(&alias)?
+                    .filter(|room| room == room_id) // Make sure it's the right room
+                    .is_none()
+            {
+                return Err(Error::BadRequest(
+                    ErrorKind::Forbidden,
+                    "You are only allowed to send canonical_alias \
+                    events when it's aliases already exists",
+                ));
+            }
+        }
+    }
+
+    let event_id = db.rooms.build_and_append_pdu(
+        PduBuilder {
+            event_type: content.event_type().into(),
+            content: json,
+            unsigned: None,
+            state_key,
+            redacts: None,
+        },
+        &sender_id,
+        &room_id,
+        &db.globals,
+        &db.sending,
+        &db.account_data,
+    )?;
+
+    Ok(event_id)
+}
diff --git a/src/client_server/sync.rs b/src/client_server/sync.rs
index 8f373544dc9ca99161cb5430b5455a53bde5e0f2..6f41160205b1b5264490f917074cc7dd7779baf5 100644
--- a/src/client_server/sync.rs
+++ b/src/client_server/sync.rs
@@ -31,7 +31,7 @@
 )]
 pub async fn sync_events_route(
     db: State<'_, Database>,
-    body: Ruma<sync_events::Request>,
+    body: Ruma<sync_events::Request<'_>>,
 ) -> ConduitResult<sync_events::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
@@ -93,76 +93,134 @@ pub async fn sync_events_route(
         let mut limited = false;
 
         let mut state_pdus = Vec::new();
-        for pdu in non_timeline_pdus {
+        for (_, pdu) in non_timeline_pdus {
             if pdu.state_key.is_some() {
                 state_pdus.push(pdu);
             }
             limited = true;
         }
 
+        // Database queries:
         let encrypted_room = db
             .rooms
             .room_state_get(&room_id, &EventType::RoomEncryption, "")?
             .is_some();
 
-        // TODO: optimize this?
-        let mut send_member_count = false;
-        let mut joined_since_last_sync = false;
-        let mut new_encrypted_room = false;
-        for (state_key, pdu) in db
+        // These type is Option<Option<_>>. The outer Option is None when there is no event between
+        // since and the current room state, meaning there should be no updates.
+        // The inner Option is None when there is an event, but there is no state hash associated
+        // with it. This can happen for the RoomCreate event, so all updates should arrive.
+        let since_state_hash = db
             .rooms
-            .pdus_since(&sender_id, &room_id, since)?
-            .filter_map(|r| r.ok())
-            .filter_map(|pdu| Some((pdu.state_key.clone()?, pdu)))
-        {
-            if pdu.kind == EventType::RoomMember {
-                send_member_count = true;
+            .pdus_after(sender_id, &room_id, since) // - 1 So we can get the event at since
+            .next()
+            .map(|pdu| db.rooms.pdu_state_hash(&pdu.ok()?.0).ok()?);
 
-                let content = serde_json::from_value::<
+        let since_members = since_state_hash.as_ref().map(|state_hash| {
+            state_hash.as_ref().and_then(|state_hash| {
+                db.rooms
+                    .state_type(&state_hash, &EventType::RoomMember)
+                    .ok()
+            })
+        });
+
+        let since_encryption = since_state_hash.as_ref().map(|state_hash| {
+            state_hash.as_ref().and_then(|state_hash| {
+                db.rooms
+                    .state_get(&state_hash, &EventType::RoomEncryption, "")
+                    .ok()
+            })
+        });
+
+        let current_members = db.rooms.room_state_type(&room_id, &EventType::RoomMember)?;
+
+        // Calculations:
+        let new_encrypted_room =
+            encrypted_room && since_encryption.map_or(false, |encryption| encryption.is_none());
+
+        let send_member_count = since_members.as_ref().map_or(false, |since_members| {
+            since_members.as_ref().map_or(true, |since_members| {
+                current_members.len() != since_members.len()
+            })
+        });
+
+        let since_sender_member = since_members.as_ref().map(|since_members| {
+            since_members.as_ref().and_then(|members| {
+                members.get(sender_id.as_str()).and_then(|pdu| {
+                    serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
+                        pdu.content.clone(),
+                    )
+                    .expect("Raw::from_value always works")
+                    .deserialize()
+                    .map_err(|_| Error::bad_database("Invalid PDU in database."))
+                    .ok()
+                })
+            })
+        });
+
+        if encrypted_room {
+            for (user_id, current_member) in current_members {
+                let current_membership = serde_json::from_value::<
                     Raw<ruma::events::room::member::MemberEventContent>,
-                >(pdu.content.clone())
+                >(current_member.content.clone())
                 .expect("Raw::from_value always works")
                 .deserialize()
-                .map_err(|_| Error::bad_database("Invalid PDU in database."))?;
+                .map_err(|_| Error::bad_database("Invalid PDU in database."))?
+                .membership;
+
+                let since_membership =
+                    since_members
+                        .as_ref()
+                        .map_or(MembershipState::Join, |members| {
+                            members
+                                .as_ref()
+                                .and_then(|members| {
+                                    members.get(&user_id).and_then(|since_member| {
+                                        serde_json::from_value::<
+                                            Raw<ruma::events::room::member::MemberEventContent>,
+                                        >(
+                                            since_member.content.clone()
+                                        )
+                                        .expect("Raw::from_value always works")
+                                        .deserialize()
+                                        .map_err(|_| {
+                                            Error::bad_database("Invalid PDU in database.")
+                                        })
+                                        .ok()
+                                    })
+                                })
+                                .map_or(MembershipState::Leave, |member| member.membership)
+                        });
 
-                if pdu.state_key == Some(sender_id.to_string())
-                    && content.membership == MembershipState::Join
-                {
-                    joined_since_last_sync = true;
-                } else if encrypted_room && content.membership == MembershipState::Join {
-                    // A new user joined an encrypted room
-                    let user_id = UserId::try_from(state_key)
-                        .map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
-                    // Add encryption update if we didn't share an encrypted room already
-                    if !share_encrypted_room(&db, &sender_id, &user_id, &room_id) {
-                        device_list_updates.insert(user_id);
+                let user_id = UserId::try_from(user_id)
+                    .map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
+
+                match (since_membership, current_membership) {
+                    (MembershipState::Leave, MembershipState::Join) => {
+                        // A new user joined an encrypted room
+                        if !share_encrypted_room(&db, &sender_id, &user_id, &room_id) {
+                            device_list_updates.insert(user_id);
+                        }
+                    }
+                    (MembershipState::Join, MembershipState::Leave) => {
+                        // Write down users that have left encrypted rooms we are in
+                        left_encrypted_users.insert(user_id);
                     }
-                } else if encrypted_room && content.membership == MembershipState::Leave {
-                    // Write down users that have left encrypted rooms we are in
-                    left_encrypted_users.insert(
-                        UserId::try_from(state_key)
-                            .map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?,
-                    );
+                    _ => {}
                 }
-            } else if pdu.kind == EventType::RoomEncryption {
-                new_encrypted_room = true;
             }
         }
 
+        let joined_since_last_sync = since_sender_member.map_or(false, |member| {
+            member.map_or(true, |member| member.membership != MembershipState::Join)
+        });
+
         if joined_since_last_sync && encrypted_room || new_encrypted_room {
             // If the user is in a new encrypted room, give them all joined users
             device_list_updates.extend(
                 db.rooms
                     .room_members(&room_id)
-                    .filter_map(|user_id| {
-                        Some(
-                            UserId::try_from(user_id.ok()?.clone())
-                                .map_err(|_| {
-                                    Error::bad_database("Invalid member event state key in db.")
-                                })
-                                .ok()?,
-                        )
-                    })
+                    .filter_map(|user_id| Some(user_id.ok()?))
                     .filter(|user_id| {
                         // Don't send key updates from the sender to the sender
                         sender_id != user_id
@@ -196,8 +254,8 @@ pub async fn sync_events_route(
                     .rooms
                     .all_pdus(&sender_id, &room_id)?
                     .filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
-                    .filter(|pdu| pdu.kind == EventType::RoomMember)
-                    .map(|pdu| {
+                    .filter(|(_, pdu)| pdu.kind == EventType::RoomMember)
+                    .map(|(_, pdu)| {
                         let content = serde_json::from_value::<
                             Raw<ruma::events::room::member::MemberEventContent>,
                         >(pdu.content.clone())
@@ -252,7 +310,7 @@ pub async fn sync_events_route(
                     (db.rooms
                         .pdus_since(&sender_id, &room_id, last_read)?
                         .filter_map(|pdu| pdu.ok()) // Filter out buggy events
-                        .filter(|pdu| {
+                        .filter(|(_, pdu)| {
                             matches!(
                                 pdu.kind.clone(),
                                 EventType::RoomMessage | EventType::RoomEncrypted
@@ -268,18 +326,15 @@ pub async fn sync_events_route(
             None
         };
 
-        let prev_batch = timeline_pdus.first().map_or(Ok::<_, Error>(None), |e| {
-            Ok(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 prev_batch = timeline_pdus
+            .first()
+            .map_or(Ok::<_, Error>(None), |(pdu_id, _)| {
+                Ok(Some(db.rooms.pdu_count(pdu_id)?.to_string()))
+            })?;
 
         let room_events = timeline_pdus
             .into_iter()
-            .map(|pdu| pdu.to_sync_room_event())
+            .map(|(_, pdu)| pdu.to_sync_room_event())
             .collect::<Vec<_>>();
 
         let mut edus = db
@@ -388,7 +443,7 @@ pub async fn sync_events_route(
         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())
+            .map(|(_, pdu)| pdu.to_sync_room_event())
             .collect();
 
         let left_room = sync_events::LeftRoom {
@@ -401,37 +456,43 @@ pub async fn sync_events_route(
             state: sync_events::State { events: Vec::new() },
         };
 
-        let mut left_since_last_sync = false;
-        for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)? {
-            let pdu = pdu?;
-            if pdu.kind == EventType::RoomMember && pdu.state_key == Some(sender_id.to_string()) {
-                let content = serde_json::from_value::<
-                    Raw<ruma::events::room::member::MemberEventContent>,
-                >(pdu.content.clone())
+        let since_member = db
+            .rooms
+            .pdus_after(sender_id, &room_id, since)
+            .next()
+            .and_then(|pdu| pdu.ok())
+            .and_then(|pdu| {
+                db.rooms
+                    .pdu_state_hash(&pdu.0)
+                    .ok()?
+                    .ok_or_else(|| Error::bad_database("Pdu in db doesn't have a state hash."))
+                    .ok()
+            })
+            .and_then(|state_hash| {
+                db.rooms
+                    .state_get(&state_hash, &EventType::RoomMember, sender_id.as_str())
+                    .ok()?
+                    .ok_or_else(|| Error::bad_database("State hash in db doesn't have a state."))
+                    .ok()
+            })
+            .and_then(|pdu| {
+                serde_json::from_value::<Raw<ruma::events::room::member::MemberEventContent>>(
+                    pdu.content,
+                )
                 .expect("Raw::from_value always works")
                 .deserialize()
-                .map_err(|_| Error::bad_database("Invalid PDU in database."))?;
+                .map_err(|_| Error::bad_database("Invalid PDU in database."))
+                .ok()
+            });
 
-                if content.membership == MembershipState::Leave {
-                    left_since_last_sync = true;
-                    break;
-                }
-            }
-        }
+        let left_since_last_sync =
+            since_member.map_or(false, |member| member.membership == MembershipState::Join);
 
         if left_since_last_sync {
             device_list_left.extend(
                 db.rooms
                     .room_members(&room_id)
-                    .filter_map(|user_id| {
-                        Some(
-                            UserId::try_from(user_id.ok()?.clone())
-                                .map_err(|_| {
-                                    Error::bad_database("Invalid member event state key in db.")
-                                })
-                                .ok()?,
-                        )
-                    })
+                    .filter_map(|user_id| Some(user_id.ok()?))
                     .filter(|user_id| {
                         // Don't send key updates from the sender to the sender
                         sender_id != user_id
@@ -454,7 +515,7 @@ pub async fn sync_events_route(
         let room_id = room_id?;
         let mut invited_since_last_sync = false;
         for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)? {
-            let pdu = pdu?;
+            let (_, pdu) = pdu?;
             if pdu.kind == EventType::RoomMember && pdu.state_key == Some(sender_id.to_string()) {
                 let content = serde_json::from_value::<
                     Raw<ruma::events::room::member::MemberEventContent>,
@@ -491,9 +552,7 @@ pub async fn sync_events_route(
     }
 
     for user_id in left_encrypted_users {
-        // If the user doesn't share an encrypted room with the target anymore, we need to tell
-        // them
-        if db
+        let still_share_encrypted_room = db
             .rooms
             .get_shared_rooms(vec![sender_id.clone(), user_id.clone()])
             .filter_map(|r| r.ok())
@@ -505,8 +564,10 @@ pub async fn sync_events_route(
                         .is_some(),
                 )
             })
-            .all(|encrypted| !encrypted)
-        {
+            .all(|encrypted| !encrypted);
+        // If the user doesn't share an encrypted room with the target anymore, we need to tell
+        // them
+        if still_share_encrypted_room {
             device_list_left.insert(user_id);
         }
     }
@@ -544,7 +605,9 @@ pub async fn sync_events_route(
             changed: device_list_updates.into_iter().collect(),
             left: device_list_left.into_iter().collect(),
         },
-        device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_id)? > since {
+        device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_id)? > since
+            || since == 0
+        {
             db.users.count_one_time_keys(sender_id, device_id)?
         } else {
             BTreeMap::new()
diff --git a/src/client_server/tag.rs b/src/client_server/tag.rs
index 99ee6e349dbf91e476c6f0f737f63dacaed36a94..d04dd3aebf86e31de1a55efe2464f24c73d3c1f3 100644
--- a/src/client_server/tag.rs
+++ b/src/client_server/tag.rs
@@ -15,7 +15,7 @@
 )]
 pub fn update_tag_route(
     db: State<'_, Database>,
-    body: Ruma<create_tag::Request>,
+    body: Ruma<create_tag::Request<'_>>,
 ) -> ConduitResult<create_tag::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -49,7 +49,7 @@ pub fn update_tag_route(
 )]
 pub fn delete_tag_route(
     db: State<'_, Database>,
-    body: Ruma<delete_tag::Request>,
+    body: Ruma<delete_tag::Request<'_>>,
 ) -> ConduitResult<delete_tag::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
@@ -80,7 +80,7 @@ pub fn delete_tag_route(
 )]
 pub fn get_tags_route(
     db: State<'_, Database>,
-    body: Ruma<get_tags::Request>,
+    body: Ruma<get_tags::Request<'_>>,
 ) -> ConduitResult<get_tags::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
diff --git a/src/client_server/to_device.rs b/src/client_server/to_device.rs
index 8c06d64e622637265adc033706fef09bd4ef7dfc..e736388d8088d1c688c29ea33f379743cd53b615 100644
--- a/src/client_server/to_device.rs
+++ b/src/client_server/to_device.rs
@@ -14,7 +14,7 @@
 )]
 pub fn send_event_to_device_route(
     db: State<'_, Database>,
-    body: Ruma<send_event_to_device::IncomingRequest>,
+    body: Ruma<send_event_to_device::Request<'_>>,
 ) -> ConduitResult<send_event_to_device::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
     let device_id = body.device_id.as_ref().expect("user is authenticated");
diff --git a/src/client_server/typing.rs b/src/client_server/typing.rs
index 89e1e4abb313b86016b2f7d82e1c56e92e2e6336..b019769483cf5856137ca2e4934596d2844db5fc 100644
--- a/src/client_server/typing.rs
+++ b/src/client_server/typing.rs
@@ -1,5 +1,6 @@
 use super::State;
 use crate::{utils, ConduitResult, Database, Ruma};
+use create_typing_event::Typing;
 use ruma::api::client::r0::typing::create_typing_event;
 
 #[cfg(feature = "conduit_bin")]
@@ -11,16 +12,15 @@
 )]
 pub fn create_typing_event_route(
     db: State<'_, Database>,
-    body: Ruma<create_typing_event::Request>,
+    body: Ruma<create_typing_event::Request<'_>>,
 ) -> ConduitResult<create_typing_event::Response> {
     let sender_id = body.sender_id.as_ref().expect("user is authenticated");
 
-    if body.typing {
+    if let Typing::Yes(duration) = body.state {
         db.rooms.edus.typing_add(
             &sender_id,
             &body.room_id,
-            body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000)
-                + utils::millis_since_unix_epoch(),
+            duration.as_millis() as u64 + utils::millis_since_unix_epoch(),
             &db.globals,
         )?;
     } else {
diff --git a/src/client_server/unversioned.rs b/src/client_server/unversioned.rs
index 3ff8bec2bd6b53cbc1eff196215faec961a5db4c..ea7f6338101ccea6fa318e29b984bb0dcaa2bcdb 100644
--- a/src/client_server/unversioned.rs
+++ b/src/client_server/unversioned.rs
@@ -1,6 +1,5 @@
 use crate::ConduitResult;
 use ruma::api::client::unversioned::get_supported_versions;
-use std::collections::BTreeMap;
 
 #[cfg(feature = "conduit_bin")]
 use rocket::get;
@@ -17,13 +16,11 @@
 /// unstable features in their stable releases
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/versions"))]
 pub fn get_supported_versions_route() -> ConduitResult<get_supported_versions::Response> {
-    let mut unstable_features = BTreeMap::new();
+    let mut resp =
+        get_supported_versions::Response::new(vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()]);
 
-    unstable_features.insert("org.matrix.e2e_cross_signing".to_owned(), true);
+    resp.unstable_features
+        .insert("org.matrix.e2e_cross_signing".to_owned(), true);
 
-    Ok(get_supported_versions::Response {
-        versions: vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()],
-        unstable_features,
-    }
-    .into())
+    Ok(resp.into())
 }
diff --git a/src/client_server/user_directory.rs b/src/client_server/user_directory.rs
index f47643c1a7170604be730d017c6bdd6172880753..dcf48fe3c7862e570e0b3f8b03ce3696ebc508e5 100644
--- a/src/client_server/user_directory.rs
+++ b/src/client_server/user_directory.rs
@@ -11,7 +11,7 @@
 )]
 pub fn search_users_route(
     db: State<'_, Database>,
-    body: Ruma<search_users::IncomingRequest>,
+    body: Ruma<search_users::Request<'_>>,
 ) -> ConduitResult<search_users::Response> {
     let limit = u64::from(body.limit) as usize;
 
diff --git a/src/database.rs b/src/database.rs
index 2bb75a58ba72ddbd08bfd047d43c04807a3528fc..4b2cba10ad90de825f0475e27cde72394a52ab1b 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -3,6 +3,7 @@
 pub mod key_backups;
 pub mod media;
 pub mod rooms;
+pub mod sending;
 pub mod transaction_ids;
 pub mod uiaa;
 pub mod users;
@@ -25,6 +26,7 @@ pub struct Database {
     pub media: media::Media,
     pub key_backups: key_backups::KeyBackups,
     pub transaction_ids: transaction_ids::TransactionIds,
+    pub sending: sending::Sending,
     pub _db: sled::Db,
 }
 
@@ -102,7 +104,6 @@ pub fn load_or_create(config: &Config) -> Result<Self> {
                 pduid_pdu: db.open_tree("pduid_pdu")?,
                 eventid_pduid: db.open_tree("eventid_pduid")?,
                 roomid_pduleaves: db.open_tree("roomid_pduleaves")?,
-                roomstateid_pdu: db.open_tree("roomstateid_pdu")?,
 
                 alias_roomid: db.open_tree("alias_roomid")?,
                 aliasid_alias: db.open_tree("alias_roomid")?,
@@ -110,12 +111,17 @@ pub fn load_or_create(config: &Config) -> Result<Self> {
 
                 tokenids: db.open_tree("tokenids")?,
 
+                roomserverids: db.open_tree("roomserverids")?,
                 userroomid_joined: db.open_tree("userroomid_joined")?,
                 roomuserid_joined: db.open_tree("roomuserid_joined")?,
                 roomuseroncejoinedids: db.open_tree("roomuseroncejoinedids")?,
                 userroomid_invited: db.open_tree("userroomid_invited")?,
                 roomuserid_invited: db.open_tree("roomuserid_invited")?,
                 userroomid_left: db.open_tree("userroomid_left")?,
+
+                stateid_pduid: db.open_tree("stateid_pduid")?,
+                pduid_statehash: db.open_tree("pduid_statehash")?,
+                roomid_statehash: db.open_tree("roomid_statehash")?,
             },
             account_data: account_data::AccountData {
                 roomuserdataid_accountdata: db.open_tree("roomuserdataid_accountdata")?,
@@ -131,6 +137,9 @@ pub fn load_or_create(config: &Config) -> Result<Self> {
             transaction_ids: transaction_ids::TransactionIds {
                 userdevicetxnid_response: db.open_tree("userdevicetxnid_response")?,
             },
+            sending: sending::Sending {
+                serverpduids: db.open_tree("serverpduids")?,
+            },
             _db: db,
         })
     }
diff --git a/src/database/globals.rs b/src/database/globals.rs
index 5db28069f822fb43c7665ef9e5cc9e44c7d56f14..37f10eec44041679b1bc9fdfd1986830a1c943ee 100644
--- a/src/database/globals.rs
+++ b/src/database/globals.rs
@@ -1,32 +1,61 @@
 use crate::{utils, Error, Result};
+use log::error;
 use ruma::ServerName;
-use std::convert::TryInto;
+use std::{convert::TryInto, sync::Arc};
 
 pub const COUNTER: &str = "c";
 
+#[derive(Clone)]
 pub struct Globals {
     pub(super) globals: sled::Tree,
-    keypair: ruma::signatures::Ed25519KeyPair,
+    keypair: Arc<ruma::signatures::Ed25519KeyPair>,
     reqwest_client: reqwest::Client,
     server_name: Box<ServerName>,
     max_request_size: u32,
     registration_disabled: bool,
     encryption_disabled: bool,
+    federation_enabled: bool,
 }
 
 impl Globals {
     pub fn load(globals: sled::Tree, config: &rocket::Config) -> Result<Self> {
-        let keypair = ruma::signatures::Ed25519KeyPair::new(
-            &*globals
-                .update_and_fetch("keypair", utils::generate_keypair)?
-                .expect("utils::generate_keypair always returns Some"),
-            "key1".to_owned(),
+        let bytes = &*globals
+            .update_and_fetch("keypair", utils::generate_keypair)?
+            .expect("utils::generate_keypair always returns Some");
+
+        let mut parts = bytes.splitn(2, |&b| b == 0xff);
+
+        let keypair = utils::string_from_bytes(
+            // 1. version
+            parts
+                .next()
+                .expect("splitn always returns at least one element"),
         )
-        .map_err(|_| Error::bad_database("Private or public keys are invalid."))?;
+        .map_err(|_| Error::bad_database("Invalid version bytes in keypair."))
+        .and_then(|version| {
+            // 2. key
+            parts
+                .next()
+                .ok_or_else(|| Error::bad_database("Invalid keypair format in database."))
+                .map(|key| (version, key))
+        })
+        .and_then(|(version, key)| {
+            ruma::signatures::Ed25519KeyPair::new(&key, version)
+                .map_err(|_| Error::bad_database("Private or public keys are invalid."))
+        });
+
+        let keypair = match keypair {
+            Ok(k) => k,
+            Err(e) => {
+                error!("Keypair invalid. Deleting...");
+                globals.remove("keypair")?;
+                return Err(e);
+            }
+        };
 
         Ok(Self {
             globals,
-            keypair,
+            keypair: Arc::new(keypair),
             reqwest_client: reqwest::Client::new(),
             server_name: config
                 .get_str("server_name")
@@ -41,6 +70,7 @@ pub fn load(globals: sled::Tree, config: &rocket::Config) -> Result<Self> {
                 .map_err(|_| Error::BadConfig("Invalid max_request_size."))?,
             registration_disabled: config.get_bool("registration_disabled").unwrap_or(false),
             encryption_disabled: config.get_bool("encryption_disabled").unwrap_or(false),
+            federation_enabled: config.get_bool("federation_enabled").unwrap_or(false),
         })
     }
 
@@ -86,4 +116,8 @@ pub fn registration_disabled(&self) -> bool {
     pub fn encryption_disabled(&self) -> bool {
         self.encryption_disabled
     }
+
+    pub fn federation_enabled(&self) -> bool {
+        self.federation_enabled
+    }
 }
diff --git a/src/database/media.rs b/src/database/media.rs
index e9dcb4a00a5ea42faa1480e90dd10d6608f30ff1..869d5d8077ba39d29d1ef8ad1fe9881d353cdc07 100644
--- a/src/database/media.rs
+++ b/src/database/media.rs
@@ -16,7 +16,7 @@ impl Media {
     pub fn create(
         &self,
         mxc: String,
-        filename: Option<&String>,
+        filename: &Option<&str>,
         content_type: &str,
         file: &[u8],
     ) -> Result<()> {
@@ -25,7 +25,31 @@ pub fn create(
         key.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
         key.extend_from_slice(&0_u32.to_be_bytes()); // Height = 0 if it's not a thumbnail
         key.push(0xff);
-        key.extend_from_slice(filename.map(|f| f.as_bytes()).unwrap_or_default());
+        key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
+        key.push(0xff);
+        key.extend_from_slice(content_type.as_bytes());
+
+        self.mediaid_file.insert(key, file)?;
+
+        Ok(())
+    }
+
+    /// Uploads or replaces a file thumbnail.
+    pub fn upload_thumbnail(
+        &self,
+        mxc: String,
+        filename: &Option<String>,
+        content_type: &str,
+        width: u32,
+        height: u32,
+        file: &[u8],
+    ) -> Result<()> {
+        let mut key = mxc.as_bytes().to_vec();
+        key.push(0xff);
+        key.extend_from_slice(&width.to_be_bytes());
+        key.extend_from_slice(&height.to_be_bytes());
+        key.push(0xff);
+        key.extend_from_slice(filename.as_ref().map(|f| f.as_bytes()).unwrap_or_default());
         key.push(0xff);
         key.extend_from_slice(content_type.as_bytes());
 
@@ -35,7 +59,7 @@ pub fn create(
     }
 
     /// Downloads a file.
-    pub fn get(&self, mxc: String) -> Result<Option<FileMeta>> {
+    pub fn get(&self, mxc: &str) -> Result<Option<FileMeta>> {
         let mut prefix = mxc.as_bytes().to_vec();
         prefix.push(0xff);
         prefix.extend_from_slice(&0_u32.to_be_bytes()); // Width = 0 if it's not a thumbnail
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index 22e61e6945bc35955f81263b41d71ae6e906157b..ab05b390cc5c33acc0692913ee72f735887d3465 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -4,105 +4,153 @@
 
 use crate::{pdu::PduBuilder, utils, Error, PduEvent, Result};
 use log::error;
+use ring::digest;
 use ruma::{
     api::client::error::ErrorKind,
     events::{
         ignored_user_list,
         room::{
-            join_rules, member,
+            member, message,
             power_levels::{self, PowerLevelsEventContent},
         },
         EventType,
     },
-    EventId, Raw, RoomAliasId, RoomId, UserId,
+    EventId, Raw, RoomAliasId, RoomId, ServerName, UserId,
 };
 use sled::IVec;
+use state_res::{event_auth, Error as StateError, Requester, StateEvent, StateMap, StateStore};
+
 use std::{
     collections::{BTreeMap, HashMap},
     convert::{TryFrom, TryInto},
     mem,
+    sync::Arc,
 };
 
+/// The unique identifier of each state group.
+///
+/// This is created when a state group is added to the database by
+/// hashing the entire state.
+pub type StateHashId = IVec;
+
+#[derive(Clone)]
 pub struct Rooms {
     pub edus: edus::RoomEdus,
     pub(super) pduid_pdu: sled::Tree, // PduId = RoomId + Count
     pub(super) eventid_pduid: sled::Tree,
     pub(super) roomid_pduleaves: sled::Tree,
-    pub(super) roomstateid_pdu: sled::Tree, // RoomStateId = Room + StateType + StateKey
-
     pub(super) alias_roomid: sled::Tree,
     pub(super) aliasid_alias: sled::Tree, // AliasId = RoomId + Count
     pub(super) publicroomids: sled::Tree,
 
     pub(super) tokenids: sled::Tree, // TokenId = RoomId + Token + PduId
 
+    /// Participating servers in a room.
+    pub(super) roomserverids: sled::Tree, // RoomServerId = RoomId + ServerName
     pub(super) userroomid_joined: sled::Tree,
     pub(super) roomuserid_joined: sled::Tree,
     pub(super) roomuseroncejoinedids: sled::Tree,
     pub(super) userroomid_invited: sled::Tree,
     pub(super) roomuserid_invited: sled::Tree,
     pub(super) userroomid_left: sled::Tree,
+
+    /// Remember the current state hash of a room.
+    pub(super) roomid_statehash: sled::Tree,
+    /// Remember the state hash at events in the past.
+    pub(super) pduid_statehash: sled::Tree,
+    /// The state for a given state hash.
+    pub(super) stateid_pduid: sled::Tree, // StateId = StateHash + EventType + StateKey
 }
 
-impl Rooms {
-    /// Checks if a room exists.
-    pub fn exists(&self, room_id: &RoomId) -> Result<bool> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
-        prefix.push(0xff);
+impl StateStore for Rooms {
+    fn get_event(
+        &self,
+        room_id: &RoomId,
+        event_id: &EventId,
+    ) -> state_res::Result<Arc<StateEvent>> {
+        let pid = self
+            .get_pdu_id(event_id)
+            .map_err(StateError::custom)?
+            .ok_or_else(|| {
+                StateError::NotFound("PDU via room_id and event_id not found in the db.".into())
+            })?;
 
-        // Look for PDUs in that room.
-        Ok(self
-            .pduid_pdu
-            .get_gt(&prefix)?
-            .filter(|(k, _)| k.starts_with(&prefix))
-            .is_some())
+        serde_json::from_slice(
+            &self
+                .pduid_pdu
+                .get(pid)
+                .map_err(StateError::custom)?
+                .ok_or_else(|| StateError::NotFound("PDU via pduid not found in db.".into()))?,
+        )
+        .map_err(Into::into)
+        .and_then(|pdu: StateEvent| {
+            // conduit's PDU's always contain a room_id but some
+            // of ruma's do not so this must be an Option
+            if pdu.room_id() == Some(room_id) {
+                Ok(Arc::new(pdu))
+            } else {
+                Err(StateError::NotFound(
+                    "Found PDU for incorrect room in db.".into(),
+                ))
+            }
+        })
     }
+}
 
-    /// Returns the full room state.
-    pub fn room_state_full(
-        &self,
-        room_id: &RoomId,
-    ) -> Result<HashMap<(EventType, String), PduEvent>> {
-        let mut hashmap = HashMap::new();
-        for pdu in self
-            .roomstateid_pdu
-            .scan_prefix(&room_id.to_string().as_bytes())
+impl Rooms {
+    /// Builds a StateMap by iterating over all keys that start
+    /// with state_hash, this gives the full state for the given state_hash.
+    pub fn state_full(&self, state_hash: &StateHashId) -> Result<StateMap<PduEvent>> {
+        self.stateid_pduid
+            .scan_prefix(&state_hash)
             .values()
-            .map(|value| {
-                Ok::<_, Error>(
-                    serde_json::from_slice::<PduEvent>(&value?)
-                        .map_err(|_| Error::bad_database("Invalid PDU in db."))?,
+            .map(|pduid| {
+                self.pduid_pdu.get(&pduid?)?.map_or_else(
+                    || Err(Error::bad_database("Failed to find StateMap.")),
+                    |b| {
+                        serde_json::from_slice::<PduEvent>(&b)
+                            .map_err(|_| Error::bad_database("Invalid PDU in db."))
+                    },
                 )
             })
-        {
-            let pdu = pdu?;
-            let state_key = pdu.state_key.clone().ok_or_else(|| {
-                Error::bad_database("Room state contains event without state_key.")
-            })?;
-            hashmap.insert((pdu.kind.clone(), state_key), pdu);
-        }
-        Ok(hashmap)
+            .map(|pdu| {
+                let pdu = pdu?;
+                Ok((
+                    (
+                        pdu.kind.clone(),
+                        pdu.state_key
+                            .as_ref()
+                            .ok_or_else(|| Error::bad_database("State event has no state key."))?
+                            .clone(),
+                    ),
+                    pdu,
+                ))
+            })
+            .collect::<Result<StateMap<_>>>()
     }
 
-    /// Returns the all state entries for this type.
-    pub fn room_state_type(
+    /// Returns all state entries for this type.
+    pub fn state_type(
         &self,
-        room_id: &RoomId,
+        state_hash: &StateHashId,
         event_type: &EventType,
     ) -> Result<HashMap<String, PduEvent>> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        let mut prefix = state_hash.to_vec();
         prefix.push(0xff);
         prefix.extend_from_slice(&event_type.to_string().as_bytes());
+        prefix.push(0xff);
 
         let mut hashmap = HashMap::new();
         for pdu in self
-            .roomstateid_pdu
+            .stateid_pduid
             .scan_prefix(&prefix)
             .values()
-            .map(|value| {
+            .map(|pdu_id| {
                 Ok::<_, Error>(
-                    serde_json::from_slice::<PduEvent>(&value?)
-                        .map_err(|_| Error::bad_database("Invalid PDU in db."))?,
+                    serde_json::from_slice::<PduEvent>(&self.pduid_pdu.get(pdu_id?)?.ok_or_else(
+                        || Error::bad_database("PDU in state not found in database."),
+                    )?)
+                    .map_err(|_| Error::bad_database("Invalid PDU bytes in room state."))?,
                 )
             })
         {
@@ -115,45 +163,169 @@ pub fn room_state_type(
         Ok(hashmap)
     }
 
-    /// Returns the full room state.
-    pub fn room_state_get(
+    /// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
+    pub fn state_get(
         &self,
-        room_id: &RoomId,
+        state_hash: &StateHashId,
         event_type: &EventType,
         state_key: &str,
     ) -> Result<Option<PduEvent>> {
-        let mut key = room_id.to_string().as_bytes().to_vec();
+        let mut key = state_hash.to_vec();
         key.push(0xff);
         key.extend_from_slice(&event_type.to_string().as_bytes());
         key.push(0xff);
         key.extend_from_slice(&state_key.as_bytes());
 
-        self.roomstateid_pdu.get(&key)?.map_or(Ok(None), |value| {
+        self.stateid_pduid.get(&key)?.map_or(Ok(None), |pdu_id| {
             Ok::<_, Error>(Some(
-                serde_json::from_slice::<PduEvent>(&value)
-                    .map_err(|_| Error::bad_database("Invalid PDU in db."))?,
+                serde_json::from_slice::<PduEvent>(
+                    &self.pduid_pdu.get(pdu_id)?.ok_or_else(|| {
+                        Error::bad_database("PDU in state not found in database.")
+                    })?,
+                )
+                .map_err(|_| Error::bad_database("Invalid PDU bytes in room state."))?,
             ))
         })
     }
 
+    /// Returns the last state hash key added to the db.
+    pub fn pdu_state_hash(&self, pdu_id: &[u8]) -> Result<Option<StateHashId>> {
+        Ok(self.pduid_statehash.get(pdu_id)?)
+    }
+
+    /// Returns the last state hash key added to the db.
+    pub fn current_state_hash(&self, room_id: &RoomId) -> Result<Option<StateHashId>> {
+        Ok(self.roomid_statehash.get(room_id.as_bytes())?)
+    }
+
+    /// This fetches auth events from the current state.
+    pub fn get_auth_events(
+        &self,
+        room_id: &RoomId,
+        kind: &EventType,
+        sender: &UserId,
+        state_key: Option<&str>,
+        content: serde_json::Value,
+    ) -> Result<StateMap<PduEvent>> {
+        let auth_events = state_res::auth_types_for_event(
+            kind.clone(),
+            sender,
+            state_key.map(|s| s.to_string()),
+            content,
+        );
+
+        let mut events = StateMap::new();
+        for (event_type, state_key) in auth_events {
+            if let Some(pdu) = self.room_state_get(room_id, &event_type, &state_key)? {
+                events.insert((event_type, state_key), pdu);
+            }
+        }
+        Ok(events)
+    }
+
+    /// Generate a new StateHash.
+    ///
+    /// A unique hash made from hashing all PDU ids of the state joined with 0xff.
+    fn calculate_hash(&self, pdu_id_bytes: &[&[u8]]) -> Result<StateHashId> {
+        // We only hash the pdu's event ids, not the whole pdu
+        let bytes = pdu_id_bytes.join(&0xff);
+        let hash = digest::digest(&digest::SHA256, &bytes);
+        Ok(hash.as_ref().into())
+    }
+
+    /// Checks if a room exists.
+    pub fn exists(&self, room_id: &RoomId) -> Result<bool> {
+        let mut prefix = room_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
+        // Look for PDUs in that room.
+        Ok(self
+            .pduid_pdu
+            .get_gt(&prefix)?
+            .filter(|(k, _)| k.starts_with(&prefix))
+            .is_some())
+    }
+
+    /// Returns the full room state.
+    pub fn force_state(
+        &self,
+        room_id: &RoomId,
+        state: HashMap<(EventType, String), Vec<u8>>,
+    ) -> Result<()> {
+        let state_hash =
+            self.calculate_hash(&state.values().map(|pdu_id| &**pdu_id).collect::<Vec<_>>())?;
+        let mut prefix = state_hash.to_vec();
+        prefix.push(0xff);
+
+        for ((event_type, state_key), pdu_id) in state {
+            let mut state_id = prefix.clone();
+            state_id.extend_from_slice(&event_type.as_str().as_bytes());
+            state_id.push(0xff);
+            state_id.extend_from_slice(&state_key.as_bytes());
+            self.stateid_pduid.insert(state_id, pdu_id)?;
+        }
+
+        self.roomid_statehash
+            .insert(room_id.as_bytes(), &*state_hash)?;
+
+        Ok(())
+    }
+
+    /// Returns the full room state.
+    pub fn room_state_full(&self, room_id: &RoomId) -> Result<StateMap<PduEvent>> {
+        if let Some(current_state_hash) = self.current_state_hash(room_id)? {
+            self.state_full(&current_state_hash)
+        } else {
+            Ok(BTreeMap::new())
+        }
+    }
+
+    /// Returns all state entries for this type.
+    pub fn room_state_type(
+        &self,
+        room_id: &RoomId,
+        event_type: &EventType,
+    ) -> Result<HashMap<String, PduEvent>> {
+        if let Some(current_state_hash) = self.current_state_hash(room_id)? {
+            self.state_type(&current_state_hash, event_type)
+        } else {
+            Ok(HashMap::new())
+        }
+    }
+
+    /// Returns a single PDU from `room_id` with key (`event_type`, `state_key`).
+    pub fn room_state_get(
+        &self,
+        room_id: &RoomId,
+        event_type: &EventType,
+        state_key: &str,
+    ) -> Result<Option<PduEvent>> {
+        if let Some(current_state_hash) = self.current_state_hash(room_id)? {
+            self.state_get(&current_state_hash, event_type, state_key)
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Returns the `count` of this pdu's id.
+    pub fn pdu_count(&self, pdu_id: &[u8]) -> Result<u64> {
+        Ok(
+            utils::u64_from_bytes(&pdu_id[pdu_id.len() - mem::size_of::<u64>()..pdu_id.len()])
+                .map_err(|_| Error::bad_database("PDU has invalid count bytes."))?,
+        )
+    }
+
     /// Returns the `count` of this pdu's id.
     pub fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
         self.eventid_pduid
-            .get(event_id.to_string().as_bytes())?
-            .map_or(Ok(None), |pdu_id| {
-                Ok(Some(
-                    utils::u64_from_bytes(
-                        &pdu_id[pdu_id.len() - mem::size_of::<u64>()..pdu_id.len()],
-                    )
-                    .map_err(|_| Error::bad_database("PDU has invalid count bytes."))?,
-                ))
-            })
+            .get(event_id.as_bytes())?
+            .map_or(Ok(None), |pdu_id| self.pdu_count(&pdu_id).map(Some))
     }
 
     /// Returns the json of a pdu.
     pub fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<serde_json::Value>> {
         self.eventid_pduid
-            .get(event_id.to_string().as_bytes())?
+            .get(event_id.as_bytes())?
             .map_or(Ok(None), |pdu_id| {
                 Ok(Some(
                     serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or_else(|| {
@@ -174,7 +346,7 @@ pub fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<IVec>> {
     /// Returns the pdu.
     pub fn get_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
         self.eventid_pduid
-            .get(event_id.to_string().as_bytes())?
+            .get(event_id.as_bytes())?
             .map_or(Ok(None), |pdu_id| {
                 Ok(Some(
                     serde_json::from_slice(&self.pduid_pdu.get(pdu_id)?.ok_or_else(|| {
@@ -194,6 +366,16 @@ pub fn get_pdu_from_id(&self, pdu_id: &IVec) -> Result<Option<PduEvent>> {
         })
     }
 
+    /// Returns the pdu.
+    pub fn get_pdu_json_from_id(&self, pdu_id: &IVec) -> Result<Option<serde_json::Value>> {
+        self.pduid_pdu.get(pdu_id)?.map_or(Ok(None), |pdu| {
+            Ok(Some(
+                serde_json::from_slice(&pdu)
+                    .map_err(|_| Error::bad_database("Invalid PDU in db."))?,
+            ))
+        })
+    }
+
     /// Removes a pdu and creates a new one with the same id.
     fn replace_pdu(&self, pdu_id: &IVec, pdu: &PduEvent) -> Result<()> {
         if self.pduid_pdu.get(&pdu_id)?.is_some() {
@@ -212,7 +394,7 @@ fn replace_pdu(&self, pdu_id: &IVec, pdu: &PduEvent) -> Result<()> {
 
     /// Returns the leaf pdus of a room.
     pub fn get_pdu_leaves(&self, room_id: &RoomId) -> Result<Vec<EventId>> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        let mut prefix = room_id.as_bytes().to_vec();
         prefix.push(0xff);
 
         let mut events = Vec::new();
@@ -238,31 +420,207 @@ pub fn get_pdu_leaves(&self, room_id: &RoomId) -> Result<Vec<EventId>> {
 
     /// Replace the leaves of a room with a new event.
     pub fn replace_pdu_leaves(&self, room_id: &RoomId, event_id: &EventId) -> Result<()> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        let mut prefix = room_id.as_bytes().to_vec();
         prefix.push(0xff);
 
         for key in self.roomid_pduleaves.scan_prefix(&prefix).keys() {
             self.roomid_pduleaves.remove(key?)?;
         }
 
-        prefix.extend_from_slice(event_id.to_string().as_bytes());
-        self.roomid_pduleaves
-            .insert(&prefix, &*event_id.to_string())?;
+        prefix.extend_from_slice(event_id.as_bytes());
+        self.roomid_pduleaves.insert(&prefix, event_id.as_bytes())?;
 
         Ok(())
     }
 
     /// Creates a new persisted data unit and adds it to a room.
-    #[allow(clippy::blocks_in_if_conditions)]
     pub fn append_pdu(
+        &self,
+        pdu: &PduEvent,
+        pdu_json: &serde_json::Value,
+        globals: &super::globals::Globals,
+        account_data: &super::account_data::AccountData,
+        sending: &super::sending::Sending,
+    ) -> Result<Vec<u8>> {
+        self.replace_pdu_leaves(&pdu.room_id, &pdu.event_id)?;
+
+        // Increment the last index and use that
+        // This is also the next_batch/since value
+        let index = globals.next_count()?;
+
+        // Mark as read first so the sending client doesn't get a notification even if appending
+        // fails
+        self.edus
+            .private_read_set(&pdu.room_id, &pdu.sender, index, &globals)?;
+
+        let room_id = pdu.room_id.clone();
+        let mut pdu_id = room_id.as_bytes().to_vec();
+        pdu_id.push(0xff);
+        pdu_id.extend_from_slice(&index.to_be_bytes());
+
+        self.pduid_pdu.insert(&pdu_id, &*pdu_json.to_string())?;
+
+        self.eventid_pduid
+            .insert(pdu.event_id.as_bytes(), &*pdu_id)?;
+
+        match pdu.kind {
+            EventType::RoomRedaction => {
+                if let Some(redact_id) = &pdu.redacts {
+                    self.redact_pdu(&redact_id, &pdu)?;
+                }
+            }
+            EventType::RoomMember => {
+                if let Some(state_key) = &pdu.state_key {
+                    // if the state_key fails
+                    let target_user_id = UserId::try_from(state_key.clone())
+                        .expect("This state_key was previously validated");
+                    // Update our membership info, we do this here incase a user is invited
+                    // and immediately leaves we need the DB to record the invite event for auth
+                    self.update_membership(
+                        &pdu.room_id,
+                        &target_user_id,
+                        serde_json::from_value::<member::MemberEventContent>(pdu.content.clone())
+                            .map_err(|_| {
+                            Error::BadRequest(
+                                ErrorKind::InvalidParam,
+                                "Invalid redaction event content.",
+                            )
+                        })?,
+                        &pdu.sender,
+                        account_data,
+                        globals,
+                    )?;
+                }
+            }
+            EventType::RoomMessage => {
+                if let Some(body) = pdu.content.get("body").and_then(|b| b.as_str()) {
+                    for word in body
+                        .split_terminator(|c: char| !c.is_alphanumeric())
+                        .map(str::to_lowercase)
+                    {
+                        let mut key = pdu.room_id.to_string().as_bytes().to_vec();
+                        key.push(0xff);
+                        key.extend_from_slice(word.as_bytes());
+                        key.push(0xff);
+                        key.extend_from_slice(&pdu_id);
+                        self.tokenids.insert(key, &[])?;
+                    }
+
+                    if body.starts_with(&format!("@conduit:{}: ", globals.server_name()))
+                        && self
+                            .id_from_alias(
+                                &format!("#admins:{}", globals.server_name())
+                                    .try_into()
+                                    .expect("#admins:server_name is a valid room alias"),
+                            )?
+                            .as_ref()
+                            == Some(&pdu.room_id)
+                    {
+                        let mut parts = body.split_whitespace().skip(1);
+                        if let Some(command) = parts.next() {
+                            let args = parts.collect::<Vec<_>>();
+
+                            self.build_and_append_pdu(
+                                PduBuilder {
+                                    event_type: EventType::RoomMessage,
+                                    content: serde_json::to_value(
+                                        message::TextMessageEventContent {
+                                            body: format!("Command: {}, Args: {:?}", command, args),
+                                            formatted: None,
+                                            relates_to: None,
+                                        },
+                                    )
+                                    .expect("event is valid, we just created it"),
+                                    unsigned: None,
+                                    state_key: None,
+                                    redacts: None,
+                                },
+                                &UserId::try_from(format!("@conduit:{}", globals.server_name()))
+                                    .expect("@conduit:server_name is valid"),
+                                &room_id,
+                                &globals,
+                                &sending,
+                                &account_data,
+                            )?;
+                        }
+                    }
+                }
+            }
+            _ => {}
+        }
+
+        Ok(pdu_id)
+    }
+
+    /// Generates a new StateHash and associates it with the incoming event.
+    ///
+    /// This adds all current state events (not including the incoming event)
+    /// to `stateid_pduid` and adds the incoming event to `pduid_statehash`.
+    /// The incoming event is the `pdu_id` passed to this method.
+    pub fn append_to_state(&self, new_pdu_id: &[u8], new_pdu: &PduEvent) -> Result<StateHashId> {
+        let old_state =
+            if let Some(old_state_hash) = self.roomid_statehash.get(new_pdu.room_id.as_bytes())? {
+                // Store state for event. The state does not include the event itself.
+                // Instead it's the state before the pdu, so the room's old state.
+                self.pduid_statehash.insert(new_pdu_id, &old_state_hash)?;
+                if new_pdu.state_key.is_none() {
+                    return Ok(old_state_hash);
+                }
+
+                let mut prefix = old_state_hash.to_vec();
+                prefix.push(0xff);
+                self.stateid_pduid
+                    .scan_prefix(&prefix)
+                    .filter_map(|pdu| pdu.map_err(|e| error!("{}", e)).ok())
+                    .map(|(k, v)| (k.subslice(prefix.len(), k.len() - prefix.len()), v))
+                    .collect::<HashMap<IVec, IVec>>()
+            } else {
+                HashMap::new()
+            };
+
+        if let Some(state_key) = &new_pdu.state_key {
+            let mut new_state = old_state;
+            let mut pdu_key = new_pdu.kind.as_str().as_bytes().to_vec();
+            pdu_key.push(0xff);
+            pdu_key.extend_from_slice(state_key.as_bytes());
+            new_state.insert(pdu_key.into(), new_pdu_id.into());
+
+            let new_state_hash =
+                self.calculate_hash(&new_state.values().map(|b| &**b).collect::<Vec<_>>())?;
+
+            let mut key = new_state_hash.to_vec();
+            key.push(0xff);
+
+            // TODO: we could avoid writing to the DB on every state event by keeping
+            // track of the delta and write that every so often
+            for (key_without_prefix, pdu_id) in new_state {
+                let mut state_id = key.clone();
+                state_id.extend_from_slice(&key_without_prefix);
+                self.stateid_pduid.insert(&state_id, &pdu_id)?;
+            }
+
+            self.roomid_statehash
+                .insert(new_pdu.room_id.as_bytes(), &*new_state_hash)?;
+
+            Ok(new_state_hash)
+        } else {
+            Err(Error::bad_database(
+                "Tried to insert non-state event into room without a state.",
+            ))
+        }
+    }
+
+    /// Creates a new persisted data unit and adds it to a room.
+    pub fn build_and_append_pdu(
         &self,
         pdu_builder: PduBuilder,
+        sender: &UserId,
+        room_id: &RoomId,
         globals: &super::globals::Globals,
+        sending: &super::sending::Sending,
         account_data: &super::account_data::AccountData,
     ) -> Result<EventId> {
         let PduBuilder {
-            room_id,
-            sender,
             event_type,
             content,
             unsigned,
@@ -272,6 +630,14 @@ pub fn append_pdu(
         // TODO: Make sure this isn't called twice in parallel
         let prev_events = self.get_pdu_leaves(&room_id)?;
 
+        let auth_events = self.get_auth_events(
+            &room_id,
+            &event_type,
+            &sender,
+            state_key.as_deref(),
+            content.clone(),
+        )?;
+
         // Is the event authorized?
         if let Some(state_key) = &state_key {
             let power_levels = self
@@ -328,142 +694,42 @@ pub fn append_pdu(
             );
 
             // Is the event allowed?
+            #[allow(clippy::blocks_in_if_conditions)]
             if !match event_type {
                 EventType::RoomEncryption => {
                     // Don't allow encryption events when it's disabled
                     !globals.encryption_disabled()
                 }
                 EventType::RoomMember => {
-                    let target_user_id = UserId::try_from(&**state_key).map_err(|_| {
-                        Error::BadRequest(
-                            ErrorKind::InvalidParam,
-                            "State key of member event does not contain user id.",
-                        )
-                    })?;
-
-                    let current_membership = self
-                        .room_state_get(
-                            &room_id,
-                            &EventType::RoomMember,
-                            &target_user_id.to_string(),
-                        )?
-                        .map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
-                            Ok(serde_json::from_value::<Raw<member::MemberEventContent>>(
-                                pdu.content,
-                            )
-                            .expect("Raw::from_value always works.")
-                            .deserialize()
-                            .map_err(|_| Error::bad_database("Invalid Member event in db."))?
-                            .membership)
-                        })?;
-
-                    let target_membership =
-                        serde_json::from_value::<Raw<member::MemberEventContent>>(content.clone())
-                            .expect("Raw::from_value always works.")
-                            .deserialize()
-                            .map_err(|_| Error::bad_database("Invalid Member event in db."))?
-                            .membership;
-
-                    let target_power = power_levels.users.get(&target_user_id).map_or_else(
-                        || {
-                            if target_membership != member::MembershipState::Join {
-                                None
-                            } else {
-                                Some(&power_levels.users_default)
-                            }
+                    let prev_event = self
+                        .get_pdu(prev_events.iter().next().ok_or(Error::BadRequest(
+                            ErrorKind::Unknown,
+                            "Membership can't be the first event",
+                        ))?)?
+                        .map(|pdu| pdu.convert_for_state_res());
+                    event_auth::valid_membership_change(
+                        // TODO this is a bit of a hack but not sure how to have a type
+                        // declared in `state_res` crate easily convert to/from conduit::PduEvent
+                        Requester {
+                            prev_event_ids: prev_events.to_owned(),
+                            room_id: &room_id,
+                            content: &content,
+                            state_key: Some(state_key.to_owned()),
+                            sender: &sender,
                         },
-                        // If it's okay, wrap with Some(_)
-                        Some,
-                    );
-
-                    let join_rules =
-                        self.room_state_get(&room_id, &EventType::RoomJoinRules, "")?
-                            .map_or(Ok::<_, Error>(join_rules::JoinRule::Public), |pdu| {
-                                Ok(serde_json::from_value::<
-                                    Raw<join_rules::JoinRulesEventContent>,
-                                >(pdu.content)
-                                .expect("Raw::from_value always works.")
-                                .deserialize()
-                                .map_err(|_| {
-                                    Error::bad_database("Database contains invalid JoinRules event")
-                                })?
-                                .join_rule)
-                            })?;
-
-                    if target_membership == member::MembershipState::Join {
-                        let mut prev_events = prev_events.iter();
-                        let prev_event = self
-                            .get_pdu(prev_events.next().ok_or(Error::BadRequest(
-                                ErrorKind::Unknown,
-                                "Membership can't be the first event",
-                            ))?)?
-                            .ok_or_else(|| {
-                                Error::bad_database("PDU leaf points to invalid event!")
-                            })?;
-                        if prev_event.kind == EventType::RoomCreate
-                            && prev_event.prev_events.is_empty()
-                        {
-                            true
-                        } else if sender != target_user_id {
-                            false
-                        } else if let member::MembershipState::Ban = current_membership {
-                            false
-                        } else {
-                            join_rules == join_rules::JoinRule::Invite
-                                && (current_membership == member::MembershipState::Join
-                                    || current_membership == member::MembershipState::Invite)
-                                || join_rules == join_rules::JoinRule::Public
-                        }
-                    } else if target_membership == member::MembershipState::Invite {
-                        if let Some(third_party_invite_json) = content.get("third_party_invite") {
-                            if current_membership == member::MembershipState::Ban {
-                                false
-                            } else {
-                                let _third_party_invite =
-                                    serde_json::from_value::<member::ThirdPartyInvite>(
-                                        third_party_invite_json.clone(),
-                                    )
-                                    .map_err(|_| {
-                                        Error::BadRequest(
-                                            ErrorKind::InvalidParam,
-                                            "ThirdPartyInvite is invalid",
-                                        )
-                                    })?;
-                                todo!("handle third party invites");
-                            }
-                        } else if sender_membership != member::MembershipState::Join
-                            || current_membership == member::MembershipState::Join
-                            || current_membership == member::MembershipState::Ban
-                        {
-                            false
-                        } else {
-                            sender_power
-                                .filter(|&p| p >= &power_levels.invite)
-                                .is_some()
-                        }
-                    } else if target_membership == member::MembershipState::Leave {
-                        if sender == target_user_id {
-                            current_membership == member::MembershipState::Join
-                                || current_membership == member::MembershipState::Invite
-                        } else if sender_membership != member::MembershipState::Join
-                            || current_membership == member::MembershipState::Ban
-                                && sender_power.filter(|&p| p < &power_levels.ban).is_some()
-                        {
-                            false
-                        } else {
-                            sender_power.filter(|&p| p >= &power_levels.kick).is_some()
-                                && target_power < sender_power
-                        }
-                    } else if target_membership == member::MembershipState::Ban {
-                        if sender_membership != member::MembershipState::Join {
-                            false
-                        } else {
-                            sender_power.filter(|&p| p >= &power_levels.ban).is_some()
-                                && target_power < sender_power
-                        }
-                    } else {
-                        false
-                    }
+                        prev_event,
+                        None,
+                        &auth_events
+                            .iter()
+                            .map(|((ty, key), pdu)| {
+                                Ok(((ty.clone(), key.clone()), pdu.convert_for_state_res()))
+                            })
+                            .collect::<Result<StateMap<_>>>()?,
+                    )
+                    .map_err(|e| {
+                        log::error!("{}", e);
+                        Error::Conflict("Found incoming PDU with invalid data.")
+                    })?
                 }
                 EventType::RoomCreate => prev_events.is_empty(),
                 // Not allow any of the following events if the sender is not joined.
@@ -474,7 +740,7 @@ pub fn append_pdu(
                         >= &power_levels.state_default
                 }
             } {
-                error!("Unauthorized");
+                error!("Unauthorized {}", event_type);
                 // Not authorized
                 return Err(Error::BadRequest(
                     ErrorKind::Forbidden,
@@ -483,7 +749,7 @@ pub fn append_pdu(
             }
         } else if !self.is_joined(&sender, &room_id)? {
             // TODO: auth rules apply to all events, not only those with a state key
-            error!("Unauthorized");
+            error!("Unauthorized {}", event_type);
             return Err(Error::BadRequest(
                 ErrorKind::Forbidden,
                 "Event is not authorized",
@@ -513,37 +779,41 @@ pub fn append_pdu(
             event_id: EventId::try_from("$thiswillbefilledinlater").expect("we know this is valid"),
             room_id: room_id.clone(),
             sender: sender.clone(),
-            origin: globals.server_name().to_owned(),
             origin_server_ts: utils::millis_since_unix_epoch()
                 .try_into()
                 .expect("time is valid"),
-            kind: event_type.clone(),
-            content: content.clone(),
-            state_key: state_key.clone(),
+            kind: event_type,
+            content,
+            state_key,
             prev_events,
             depth: depth
                 .try_into()
                 .map_err(|_| Error::bad_database("Depth is invalid"))?,
-            auth_events: Vec::new(),
-            redacts: redacts.clone(),
+            auth_events: auth_events
+                .into_iter()
+                .map(|(_, pdu)| pdu.event_id)
+                .collect(),
+            redacts,
             unsigned,
             hashes: ruma::events::pdu::EventHash {
                 sha256: "aaa".to_owned(),
             },
-            signatures: HashMap::new(),
+            signatures: BTreeMap::new(),
         };
 
-        // Generate event id
-        pdu.event_id = EventId::try_from(&*format!(
-            "${}",
-            ruma::signatures::reference_hash(
-                &serde_json::to_value(&pdu).expect("event is valid, we just created it")
-            )
-            .expect("ruma can calculate reference hashes")
-        ))
-        .expect("ruma's reference hashes are valid event ids");
-
+        // Hash and sign
         let mut pdu_json = serde_json::to_value(&pdu).expect("event is valid, we just created it");
+        pdu_json
+            .as_object_mut()
+            .expect("json is object")
+            .remove("event_id");
+
+        // Add origin because synapse likes that (and it's required in the spec)
+        pdu_json
+            .as_object_mut()
+            .expect("json is object")
+            .insert("origin".to_owned(), globals.server_name().as_str().into());
+
         ruma::signatures::hash_and_sign_event(
             globals.server_name().as_str(),
             globals.keypair(),
@@ -551,79 +821,30 @@ pub fn append_pdu(
         )
         .expect("event is valid, we just created it");
 
-        self.replace_pdu_leaves(&room_id, &pdu.event_id)?;
-
-        // Increment the last index and use that
-        // This is also the next_batch/since value
-        let index = globals.next_count()?;
-
-        let mut pdu_id = room_id.to_string().as_bytes().to_vec();
-        pdu_id.push(0xff);
-        pdu_id.extend_from_slice(&index.to_be_bytes());
+        // Generate event id
+        pdu.event_id = EventId::try_from(&*format!(
+            "${}",
+            ruma::signatures::reference_hash(&pdu_json)
+                .expect("ruma can calculate reference hashes")
+        ))
+        .expect("ruma's reference hashes are valid event ids");
 
-        self.pduid_pdu.insert(&pdu_id, &*pdu_json.to_string())?;
+        pdu_json
+            .as_object_mut()
+            .expect("json is object")
+            .insert("event_id".to_owned(), pdu.event_id.to_string().into());
 
-        self.eventid_pduid
-            .insert(pdu.event_id.to_string(), pdu_id.clone())?;
+        let pdu_id = self.append_pdu(&pdu, &pdu_json, globals, account_data, sending)?;
 
-        if let Some(state_key) = &pdu.state_key {
-            let mut key = room_id.to_string().as_bytes().to_vec();
-            key.push(0xff);
-            key.extend_from_slice(pdu.kind.to_string().as_bytes());
-            key.push(0xff);
-            key.extend_from_slice(state_key.as_bytes());
-            self.roomstateid_pdu.insert(key, &*pdu_json.to_string())?;
-        }
+        self.append_to_state(&pdu_id, &pdu)?;
 
-        match event_type {
-            EventType::RoomRedaction => {
-                if let Some(redact_id) = &redacts {
-                    self.redact_pdu(&redact_id, &pdu)?;
-                }
-            }
-            EventType::RoomMember => {
-                if let Some(state_key) = state_key {
-                    // if the state_key fails
-                    let target_user_id = UserId::try_from(state_key)
-                        .expect("This state_key was previously validated");
-                    // Update our membership info, we do this here incase a user is invited
-                    // and immediately leaves we need the DB to record the invite event for auth
-                    self.update_membership(
-                        &room_id,
-                        &target_user_id,
-                        serde_json::from_value::<member::MemberEventContent>(content).map_err(
-                            |_| {
-                                Error::BadRequest(
-                                    ErrorKind::InvalidParam,
-                                    "Invalid redaction event content.",
-                                )
-                            },
-                        )?,
-                        &sender,
-                        account_data,
-                        globals,
-                    )?;
-                }
-            }
-            EventType::RoomMessage => {
-                if let Some(body) = content.get("body").and_then(|b| b.as_str()) {
-                    for word in body
-                        .split_terminator(|c: char| !c.is_alphanumeric())
-                        .map(str::to_lowercase)
-                    {
-                        let mut key = room_id.to_string().as_bytes().to_vec();
-                        key.push(0xff);
-                        key.extend_from_slice(word.as_bytes());
-                        key.push(0xff);
-                        key.extend_from_slice(&pdu_id);
-                        self.tokenids.insert(key, &[])?;
-                    }
-                }
-            }
-            _ => {}
+        for server in self
+            .room_servers(room_id)
+            .filter_map(|r| r.ok())
+            .filter(|server| &**server != globals.server_name())
+        {
+            sending.send_pdu(server, &pdu_id)?;
         }
-        self.edus
-            .private_read_set(&room_id, &sender, index, &globals)?;
 
         Ok(pdu.event_id)
     }
@@ -633,7 +854,7 @@ pub fn all_pdus(
         &self,
         user_id: &UserId,
         room_id: &RoomId,
-    ) -> Result<impl Iterator<Item = Result<PduEvent>>> {
+    ) -> Result<impl Iterator<Item = Result<(IVec, PduEvent)>>> {
         self.pdus_since(user_id, room_id, 0)
     }
 
@@ -644,7 +865,7 @@ pub fn pdus_since(
         user_id: &UserId,
         room_id: &RoomId,
         since: u64,
-    ) -> Result<impl DoubleEndedIterator<Item = Result<PduEvent>>> {
+    ) -> Result<impl DoubleEndedIterator<Item = Result<(IVec, PduEvent)>>> {
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
 
@@ -660,13 +881,13 @@ pub fn pdus_since(
             .pduid_pdu
             .range(first_pdu_id..last_pdu_id)
             .filter_map(|r| r.ok())
-            .map(move |(_, v)| {
+            .map(move |(pdu_id, v)| {
                 let mut pdu = serde_json::from_slice::<PduEvent>(&v)
                     .map_err(|_| Error::bad_database("PDU in db is invalid."))?;
                 if pdu.sender != user_id {
                     pdu.unsigned.remove("transaction_id");
                 }
-                Ok(pdu)
+                Ok((pdu_id, pdu))
             }))
     }
 
@@ -677,7 +898,7 @@ pub fn pdus_until(
         user_id: &UserId,
         room_id: &RoomId,
         until: u64,
-    ) -> impl Iterator<Item = Result<(u64, PduEvent)>> {
+    ) -> impl Iterator<Item = Result<(IVec, PduEvent)>> {
         // Create the first part of the full pdu id
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
@@ -688,23 +909,18 @@ pub fn pdus_until(
         let current: &[u8] = &current;
 
         let user_id = user_id.clone();
-        let prefixlen = prefix.len();
         self.pduid_pdu
             .range(..current)
             .rev()
             .filter_map(|r| r.ok())
             .take_while(move |(k, _)| k.starts_with(&prefix))
-            .map(move |(k, v)| {
+            .map(move |(pdu_id, v)| {
                 let mut pdu = serde_json::from_slice::<PduEvent>(&v)
                     .map_err(|_| Error::bad_database("PDU in db is invalid."))?;
                 if pdu.sender != user_id {
                     pdu.unsigned.remove("transaction_id");
                 }
-                Ok((
-                    utils::u64_from_bytes(&k[prefixlen..])
-                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?,
-                    pdu,
-                ))
+                Ok((pdu_id, pdu))
             })
     }
 
@@ -715,7 +931,7 @@ pub fn pdus_after(
         user_id: &UserId,
         room_id: &RoomId,
         from: u64,
-    ) -> impl Iterator<Item = Result<(u64, PduEvent)>> {
+    ) -> impl Iterator<Item = Result<(IVec, PduEvent)>> {
         // Create the first part of the full pdu id
         let mut prefix = room_id.to_string().as_bytes().to_vec();
         prefix.push(0xff);
@@ -726,22 +942,17 @@ pub fn pdus_after(
         let current: &[u8] = &current;
 
         let user_id = user_id.clone();
-        let prefixlen = prefix.len();
         self.pduid_pdu
             .range(current..)
             .filter_map(|r| r.ok())
             .take_while(move |(k, _)| k.starts_with(&prefix))
-            .map(move |(k, v)| {
+            .map(move |(pdu_id, v)| {
                 let mut pdu = serde_json::from_slice::<PduEvent>(&v)
                     .map_err(|_| Error::bad_database("PDU in db is invalid."))?;
                 if pdu.sender != user_id {
                     pdu.unsigned.remove("transaction_id");
                 }
-                Ok((
-                    utils::u64_from_bytes(&k[prefixlen..])
-                        .map_err(|_| Error::bad_database("Invalid pdu id in db."))?,
-                    pdu,
-                ))
+                Ok((pdu_id, pdu))
             })
     }
 
@@ -767,19 +978,24 @@ fn update_membership(
         &self,
         room_id: &RoomId,
         user_id: &UserId,
-        mut member_content: member::MemberEventContent,
+        member_content: member::MemberEventContent,
         sender: &UserId,
         account_data: &super::account_data::AccountData,
         globals: &super::globals::Globals,
     ) -> Result<()> {
         let membership = member_content.membership;
-        let mut userroom_id = user_id.to_string().as_bytes().to_vec();
+
+        let mut roomserver_id = room_id.as_bytes().to_vec();
+        roomserver_id.push(0xff);
+        roomserver_id.extend_from_slice(user_id.server_name().as_bytes());
+
+        let mut userroom_id = user_id.as_bytes().to_vec();
         userroom_id.push(0xff);
-        userroom_id.extend_from_slice(room_id.to_string().as_bytes());
+        userroom_id.extend_from_slice(room_id.as_bytes());
 
-        let mut roomuser_id = room_id.to_string().as_bytes().to_vec();
+        let mut roomuser_id = room_id.as_bytes().to_vec();
         roomuser_id.push(0xff);
-        roomuser_id.extend_from_slice(user_id.to_string().as_bytes());
+        roomuser_id.extend_from_slice(user_id.as_bytes());
 
         match &membership {
             member::MembershipState::Join => {
@@ -789,19 +1005,17 @@ fn update_membership(
                     self.roomuseroncejoinedids.insert(&userroom_id, &[])?;
 
                     // Check if the room has a predecessor
-                    if let Some(predecessor) = serde_json::from_value::<
-                        Raw<ruma::events::room::create::CreateEventContent>,
-                    >(
-                        self.room_state_get(&room_id, &EventType::RoomCreate, "")?
-                            .ok_or_else(|| {
-                                Error::bad_database("Found room without m.room.create event.")
-                            })?
-                            .content,
-                    )
-                    .expect("Raw::from_value always works")
-                    .deserialize()
-                    .map_err(|_| Error::bad_database("Invalid room event in database."))?
-                    .predecessor
+                    if let Some(predecessor) = self
+                        .room_state_get(&room_id, &EventType::RoomCreate, "")?
+                        .and_then(|create| {
+                            serde_json::from_value::<
+                                Raw<ruma::events::room::create::CreateEventContent>,
+                            >(create.content)
+                            .expect("Raw::from_value always works")
+                            .deserialize()
+                            .ok()
+                        })
+                        .and_then(|content| content.predecessor)
                     {
                         // Copy user settings from predecessor to the current room:
                         // - Push rules
@@ -868,6 +1082,7 @@ fn update_membership(
                     }
                 }
 
+                self.roomserverids.insert(&roomserver_id, &[])?;
                 self.userroomid_joined.insert(&userroom_id, &[])?;
                 self.roomuserid_joined.insert(&roomuser_id, &[])?;
                 self.userroomid_invited.remove(&userroom_id)?;
@@ -887,25 +1102,10 @@ fn update_membership(
                     });
 
                 if is_ignored {
-                    member_content.membership = member::MembershipState::Leave;
-
-                    self.append_pdu(
-                        PduBuilder {
-                            room_id: room_id.clone(),
-                            sender: user_id.clone(),
-                            event_type: EventType::RoomMember,
-                            content: serde_json::to_value(member_content)
-                                .expect("event is valid, we just created it"),
-                            unsigned: None,
-                            state_key: Some(user_id.to_string()),
-                            redacts: None,
-                        },
-                        globals,
-                        account_data,
-                    )?;
-
                     return Ok(());
                 }
+
+                self.roomserverids.insert(&roomserver_id, &[])?;
                 self.userroomid_invited.insert(&userroom_id, &[])?;
                 self.roomuserid_invited.insert(&roomuser_id, &[])?;
                 self.userroomid_joined.remove(&userroom_id)?;
@@ -913,6 +1113,14 @@ fn update_membership(
                 self.userroomid_left.remove(&userroom_id)?;
             }
             member::MembershipState::Leave | member::MembershipState::Ban => {
+                if self
+                    .room_members(room_id)
+                    .chain(self.room_members_invited(room_id))
+                    .filter_map(|r| r.ok())
+                    .all(|u| u.server_name() != user_id.server_name())
+                {
+                    self.roomserverids.remove(&roomserver_id)?;
+                }
                 self.userroomid_left.insert(&userroom_id, &[])?;
                 self.userroomid_joined.remove(&userroom_id)?;
                 self.roomuserid_joined.remove(&roomuser_id)?;
@@ -927,9 +1135,9 @@ fn update_membership(
 
     /// Makes a user forget a room.
     pub fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()> {
-        let mut userroom_id = user_id.to_string().as_bytes().to_vec();
+        let mut userroom_id = user_id.as_bytes().to_vec();
         userroom_id.push(0xff);
-        userroom_id.extend_from_slice(room_id.to_string().as_bytes());
+        userroom_id.extend_from_slice(room_id.as_bytes());
 
         self.userroomid_left.remove(userroom_id)?;
 
@@ -945,8 +1153,8 @@ pub fn set_alias(
         if let Some(room_id) = room_id {
             // New alias
             self.alias_roomid
-                .insert(alias.alias(), &*room_id.to_string())?;
-            let mut aliasid = room_id.to_string().as_bytes().to_vec();
+                .insert(alias.alias(), room_id.as_bytes())?;
+            let mut aliasid = room_id.as_bytes().to_vec();
             aliasid.extend_from_slice(&globals.next_count()?.to_be_bytes());
             self.aliasid_alias.insert(aliasid, &*alias.alias())?;
         } else {
@@ -981,7 +1189,7 @@ pub fn id_from_alias(&self, alias: &RoomAliasId) -> Result<Option<RoomId>> {
     }
 
     pub fn room_aliases(&self, room_id: &RoomId) -> impl Iterator<Item = Result<RoomAliasId>> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        let mut prefix = room_id.as_bytes().to_vec();
         prefix.push(0xff);
 
         self.aliasid_alias
@@ -995,16 +1203,16 @@ pub fn room_aliases(&self, room_id: &RoomId) -> impl Iterator<Item = Result<Room
 
     pub fn set_public(&self, room_id: &RoomId, public: bool) -> Result<()> {
         if public {
-            self.publicroomids.insert(room_id.to_string(), &[])?;
+            self.publicroomids.insert(room_id.as_bytes(), &[])?;
         } else {
-            self.publicroomids.remove(room_id.to_string())?;
+            self.publicroomids.remove(room_id.as_bytes())?;
         }
 
         Ok(())
     }
 
     pub fn is_public_room(&self, room_id: &RoomId) -> Result<bool> {
-        Ok(self.publicroomids.contains_key(room_id.to_string())?)
+        Ok(self.publicroomids.contains_key(room_id.as_bytes())?)
     }
 
     pub fn public_rooms(&self) -> impl Iterator<Item = Result<RoomId>> {
@@ -1023,7 +1231,7 @@ pub fn search_pdus<'a>(
         room_id: &RoomId,
         search_string: &str,
     ) -> Result<(impl Iterator<Item = IVec> + 'a, Vec<String>)> {
-        let mut prefix = room_id.to_string().as_bytes().to_vec();
+        let mut prefix = room_id.as_bytes().to_vec();
         prefix.push(0xff);
 
         let words = search_string
@@ -1083,8 +1291,7 @@ pub fn get_shared_rooms<'a>(
                     let roomid_index = key
                         .iter()
                         .enumerate()
-                        .filter(|(_, &b)| b == 0xff)
-                        .nth(0)
+                        .find(|(_, &b)| b == 0xff)
                         .ok_or_else(|| Error::bad_database("Invalid userroomid_joined in db."))?
                         .0
                         + 1; // +1 because the room id starts AFTER the separator
@@ -1107,10 +1314,34 @@ pub fn get_shared_rooms<'a>(
             })
     }
 
+    /// Returns an iterator over all joined members of a room.
+    pub fn room_servers(&self, room_id: &RoomId) -> impl Iterator<Item = Result<Box<ServerName>>> {
+        let mut prefix = room_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
+        self.roomserverids.scan_prefix(prefix).keys().map(|key| {
+            Ok(Box::<ServerName>::try_from(
+                utils::string_from_bytes(
+                    &key?
+                        .rsplit(|&b| b == 0xff)
+                        .next()
+                        .expect("rsplit always returns an element"),
+                )
+                .map_err(|_| {
+                    Error::bad_database("Server name in roomserverids is invalid unicode.")
+                })?,
+            )
+            .map_err(|_| Error::bad_database("Server name in roomserverids is invalid."))?)
+        })
+    }
+
     /// Returns an iterator over all joined members of a room.
     pub fn room_members(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
+        let mut prefix = room_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
         self.roomuserid_joined
-            .scan_prefix(room_id.to_string())
+            .scan_prefix(prefix)
             .keys()
             .map(|key| {
                 Ok(UserId::try_from(
@@ -1130,8 +1361,11 @@ pub fn room_members(&self, room_id: &RoomId) -> impl Iterator<Item = Result<User
 
     /// Returns an iterator over all User IDs who ever joined a room.
     pub fn room_useroncejoined(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
+        let mut prefix = room_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
         self.roomuseroncejoinedids
-            .scan_prefix(room_id.to_string())
+            .scan_prefix(prefix)
             .keys()
             .map(|key| {
                 Ok(UserId::try_from(
@@ -1151,8 +1385,11 @@ pub fn room_useroncejoined(&self, room_id: &RoomId) -> impl Iterator<Item = Resu
 
     /// Returns an iterator over all invited members of a room.
     pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
+        let mut prefix = room_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
         self.roomuserid_invited
-            .scan_prefix(room_id.to_string())
+            .scan_prefix(prefix)
             .keys()
             .map(|key| {
                 Ok(UserId::try_from(
@@ -1173,7 +1410,7 @@ pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Res
     /// 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())
+            .scan_prefix(user_id.as_bytes())
             .keys()
             .map(|key| {
                 Ok(RoomId::try_from(
@@ -1193,8 +1430,11 @@ pub fn rooms_joined(&self, user_id: &UserId) -> impl Iterator<Item = Result<Room
 
     /// Returns an iterator over all rooms a user was invited to.
     pub fn rooms_invited(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
+        let mut prefix = user_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
         self.userroomid_invited
-            .scan_prefix(&user_id.to_string())
+            .scan_prefix(prefix)
             .keys()
             .map(|key| {
                 Ok(RoomId::try_from(
@@ -1214,23 +1454,23 @@ pub fn rooms_invited(&self, user_id: &UserId) -> impl Iterator<Item = Result<Roo
 
     /// Returns an iterator over all rooms a user left.
     pub fn rooms_left(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
-        self.userroomid_left
-            .scan_prefix(&user_id.to_string())
-            .keys()
-            .map(|key| {
-                Ok(RoomId::try_from(
-                    utils::string_from_bytes(
-                        &key?
-                            .rsplit(|&b| b == 0xff)
-                            .next()
-                            .expect("rsplit always returns an element"),
-                    )
-                    .map_err(|_| {
-                        Error::bad_database("Room ID in userroomid_left is invalid unicode.")
-                    })?,
+        let mut prefix = user_id.as_bytes().to_vec();
+        prefix.push(0xff);
+
+        self.userroomid_left.scan_prefix(prefix).keys().map(|key| {
+            Ok(RoomId::try_from(
+                utils::string_from_bytes(
+                    &key?
+                        .rsplit(|&b| b == 0xff)
+                        .next()
+                        .expect("rsplit always returns an element"),
                 )
-                .map_err(|_| Error::bad_database("Room ID in userroomid_left is invalid."))?)
-            })
+                .map_err(|_| {
+                    Error::bad_database("Room ID in userroomid_left is invalid unicode.")
+                })?,
+            )
+            .map_err(|_| Error::bad_database("Room ID in userroomid_left is invalid."))?)
+        })
     }
 
     pub fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
@@ -1242,25 +1482,25 @@ pub fn once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
     }
 
     pub fn is_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
-        let mut userroom_id = user_id.to_string().as_bytes().to_vec();
+        let mut userroom_id = user_id.as_bytes().to_vec();
         userroom_id.push(0xff);
-        userroom_id.extend_from_slice(room_id.to_string().as_bytes());
+        userroom_id.extend_from_slice(room_id.as_bytes());
 
         Ok(self.userroomid_joined.get(userroom_id)?.is_some())
     }
 
     pub fn is_invited(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
-        let mut userroom_id = user_id.to_string().as_bytes().to_vec();
+        let mut userroom_id = user_id.as_bytes().to_vec();
         userroom_id.push(0xff);
-        userroom_id.extend_from_slice(room_id.to_string().as_bytes());
+        userroom_id.extend_from_slice(room_id.as_bytes());
 
         Ok(self.userroomid_invited.get(userroom_id)?.is_some())
     }
 
     pub fn is_left(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
-        let mut userroom_id = user_id.to_string().as_bytes().to_vec();
+        let mut userroom_id = user_id.as_bytes().to_vec();
         userroom_id.push(0xff);
-        userroom_id.extend_from_slice(room_id.to_string().as_bytes());
+        userroom_id.extend_from_slice(room_id.as_bytes());
 
         Ok(self.userroomid_left.get(userroom_id)?.is_some())
     }
diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs
index d60e1f16899efa62bc70b70d4bd34bf4735598ef..29f5407b605f6335ea75c47492d41f6e32c305f6 100644
--- a/src/database/rooms/edus.rs
+++ b/src/database/rooms/edus.rs
@@ -11,8 +11,10 @@
 use std::{
     collections::HashMap,
     convert::{TryFrom, TryInto},
+    mem,
 };
 
+#[derive(Clone)]
 pub struct RoomEdus {
     pub(in super::super) readreceiptid_readreceipt: sled::Tree, // ReadReceiptId = RoomId + Count + UserId
     pub(in super::super) roomuserid_privateread: sled::Tree, // RoomUserId = Room + User, PrivateRead = Count
@@ -227,9 +229,11 @@ fn typings_maintain(
                 let key = key?;
                 Ok::<_, Error>((
                     key.clone(),
-                    utils::u64_from_bytes(key.split(|&b| b == 0xff).nth(1).ok_or_else(|| {
-                        Error::bad_database("RoomTyping has invalid timestamp or delimiters.")
-                    })?)
+                    utils::u64_from_bytes(
+                        &key.splitn(2, |&b| b == 0xff).nth(1).ok_or_else(|| {
+                            Error::bad_database("RoomTyping has invalid timestamp or delimiters.")
+                        })?[0..mem::size_of::<u64>()],
+                    )
                     .map_err(|_| Error::bad_database("RoomTyping has invalid timestamp bytes."))?,
                 ))
             })
diff --git a/src/database/sending.rs b/src/database/sending.rs
new file mode 100644
index 0000000000000000000000000000000000000000..24a783b670ea14432270a966856569a7499daae1
--- /dev/null
+++ b/src/database/sending.rs
@@ -0,0 +1,114 @@
+use std::{collections::HashSet, convert::TryFrom, time::SystemTime};
+
+use crate::{server_server, utils, Error, PduEvent, Result};
+use federation::transactions::send_transaction_message;
+use log::warn;
+use rocket::futures::stream::{FuturesUnordered, StreamExt};
+use ruma::{api::federation, ServerName};
+use sled::IVec;
+use tokio::select;
+
+pub struct Sending {
+    /// The state for a given state hash.
+    pub(super) serverpduids: sled::Tree, // ServerPduId = ServerName + PduId
+}
+
+impl Sending {
+    pub fn start_handler(&self, globals: &super::globals::Globals, rooms: &super::rooms::Rooms) {
+        let serverpduids = self.serverpduids.clone();
+        let rooms = rooms.clone();
+        let globals = globals.clone();
+
+        tokio::spawn(async move {
+            let mut futures = FuturesUnordered::new();
+            let mut waiting_servers = HashSet::new();
+
+            let mut subscriber = serverpduids.watch_prefix(b"");
+            loop {
+                select! {
+                    Some(server) = futures.next() => {
+                        warn!("response: {:?}", &server);
+                        warn!("futures left: {}", &futures.len());
+                        match server {
+                            Ok((server, _response)) => {
+                                waiting_servers.remove(&server)
+                            }
+                            Err((server, _e)) => {
+                                waiting_servers.remove(&server)
+                            }
+                        };
+                    },
+                    Some(event) = &mut subscriber => {
+                        if let sled::Event::Insert { key, .. } = event {
+                            let serverpduid = key.clone();
+                            let mut parts = serverpduid.splitn(2, |&b| b == 0xff);
+
+                            if let Some((server, pdu_id)) = utils::string_from_bytes(
+                                    parts
+                                        .next()
+                                        .expect("splitn will always return 1 or more elements"),
+                                )
+                                .map_err(|_| Error::bad_database("ServerName in serverpduid bytes are invalid."))
+                                .and_then(|server_str|Box::<ServerName>::try_from(server_str)
+                                    .map_err(|_| Error::bad_database("ServerName in serverpduid is invalid.")))
+                                .ok()
+                                .filter(|server| waiting_servers.insert(server.clone()))
+                                .and_then(|server| parts
+                                .next()
+                                .ok_or_else(|| Error::bad_database("Invalid serverpduid in db.")).ok().map(|pdu_id| (server, pdu_id)))
+                            {
+                                futures.push(Self::handle_event(server, pdu_id.into(), &globals, &rooms));
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    pub fn send_pdu(&self, server: Box<ServerName>, pdu_id: &[u8]) -> Result<()> {
+        let mut key = server.as_bytes().to_vec();
+        key.push(0xff);
+        key.extend_from_slice(pdu_id);
+        self.serverpduids.insert(key, b"")?;
+
+        Ok(())
+    }
+
+    async fn handle_event(
+        server: Box<ServerName>,
+        pdu_id: IVec,
+        globals: &super::globals::Globals,
+        rooms: &super::rooms::Rooms,
+    ) -> std::result::Result<
+        (Box<ServerName>, send_transaction_message::v1::Response),
+        (Box<ServerName>, Error),
+    > {
+        let pdu_json = PduEvent::convert_to_outgoing_federation_event(
+            rooms
+                .get_pdu_json_from_id(&pdu_id)
+                .map_err(|e| (server.clone(), e))?
+                .ok_or_else(|| {
+                    (
+                        server.clone(),
+                        Error::bad_database("Event in serverpduids not found in db."),
+                    )
+                })?,
+        );
+
+        server_server::send_request(
+            &globals,
+            server.clone(),
+            send_transaction_message::v1::Request {
+                origin: globals.server_name(),
+                pdus: &[pdu_json],
+                edus: &[],
+                origin_server_ts: SystemTime::now(),
+                transaction_id: &utils::random_string(16),
+            },
+        )
+        .await
+        .map(|response| (server.clone(), response))
+        .map_err(|e| (server, e))
+    }
+}
diff --git a/src/database/uiaa.rs b/src/database/uiaa.rs
index cece8dbe30eda53888de271258bf4b7d823c6ebc..e318f43681b51f1ca9ddf27d5df4b6104467e007 100644
--- a/src/database/uiaa.rs
+++ b/src/database/uiaa.rs
@@ -2,7 +2,7 @@
 use ruma::{
     api::client::{
         error::ErrorKind,
-        r0::uiaa::{AuthData, UiaaInfo},
+        r0::uiaa::{IncomingAuthData, UiaaInfo},
     },
     DeviceId, UserId,
 };
@@ -26,12 +26,12 @@ pub fn try_auth(
         &self,
         user_id: &UserId,
         device_id: &DeviceId,
-        auth: &AuthData,
+        auth: &IncomingAuthData,
         uiaainfo: &UiaaInfo,
         users: &super::users::Users,
         globals: &super::globals::Globals,
     ) -> Result<(bool, UiaaInfo)> {
-        if let AuthData::DirectRequest {
+        if let IncomingAuthData::DirectRequest {
             kind,
             session,
             auth_parameters,
diff --git a/src/database/users.rs b/src/database/users.rs
index 1b6a6812e877a049b4ac591daba3b8bc2f747f85..0d35e36250dcb1b199e3655a815a670239768ee5 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -8,7 +8,7 @@
             keys::{CrossSigningKey, OneTimeKey},
         },
     },
-    encryption::DeviceKeys,
+    encryption::IncomingDeviceKeys,
     events::{AnyToDeviceEvent, EventType},
     DeviceId, DeviceKeyAlgorithm, DeviceKeyId, Raw, UserId,
 };
@@ -57,6 +57,11 @@ pub fn create(&self, user_id: &UserId, password: &str) -> Result<()> {
         Ok(())
     }
 
+    /// Returns the number of users registered on this server.
+    pub fn count(&self) -> usize {
+        self.userid_password.iter().count()
+    }
+
     /// Find out which user an access token belongs to.
     pub fn find_from_token(&self, token: &str) -> Result<Option<(UserId, String)>> {
         self.token_userdeviceid
@@ -395,7 +400,7 @@ pub fn add_device_keys(
         &self,
         user_id: &UserId,
         device_id: &DeviceId,
-        device_keys: &DeviceKeys,
+        device_keys: &IncomingDeviceKeys,
         rooms: &super::rooms::Rooms,
         globals: &super::globals::Globals,
     ) -> Result<()> {
@@ -603,7 +608,7 @@ fn mark_device_key_update(
                 .room_state_get(&room_id, &EventType::RoomEncryption, "")?
                 .is_none()
             {
-                return Ok(());
+                continue;
             }
 
             let mut key = room_id.to_string().as_bytes().to_vec();
@@ -625,7 +630,7 @@ pub fn get_device_keys(
         &self,
         user_id: &UserId,
         device_id: &DeviceId,
-    ) -> Result<Option<DeviceKeys>> {
+    ) -> Result<Option<IncomingDeviceKeys>> {
         let mut key = user_id.to_string().as_bytes().to_vec();
         key.push(0xff);
         key.extend_from_slice(device_id.as_bytes());
diff --git a/src/error.rs b/src/error.rs
index 623aa0ef94f873d3b9749104a9b7becb553fa142..f521da43de37a02a2694c1d4427b8c957efafd87 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -70,14 +70,14 @@ fn respond_to(self, r: &'r Request<'_>) -> response::Result<'o> {
         use ErrorKind::*;
         let (kind, status_code) = match self {
             Self::BadRequest(kind, _) => (
-                kind,
+                kind.clone(),
                 match kind {
                     Forbidden | GuestAccessForbidden | ThreepidAuthFailed | ThreepidDenied => {
                         StatusCode::FORBIDDEN
                     }
-                    Unauthorized | UnknownToken | MissingToken => StatusCode::UNAUTHORIZED,
+                    Unauthorized | UnknownToken { .. } | MissingToken => StatusCode::UNAUTHORIZED,
                     NotFound => StatusCode::NOT_FOUND,
-                    LimitExceeded => StatusCode::TOO_MANY_REQUESTS,
+                    LimitExceeded { .. } => StatusCode::TOO_MANY_REQUESTS,
                     UserDeactivated => StatusCode::FORBIDDEN,
                     TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
                     _ => StatusCode::BAD_REQUEST,
diff --git a/src/main.rs b/src/main.rs
index eb060e3eca8f09b961a0bfae3baa11375e15da57..8fb5fda9ff9b1f13be3b1f695d31b7820af99d8a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -119,17 +119,21 @@ fn setup_rocket() -> rocket::Rocket {
                 client_server::get_pushers_route,
                 client_server::set_pushers_route,
                 client_server::upgrade_room_route,
-                server_server::well_known_server,
                 server_server::get_server_version,
                 server_server::get_server_keys,
                 server_server::get_server_keys_deprecated,
                 server_server::get_public_rooms_route,
+                server_server::get_public_rooms_filtered_route,
                 server_server::send_transaction_message_route,
+                server_server::get_missing_events_route,
+                server_server::get_profile_information_route,
             ],
         )
         .attach(AdHoc::on_attach("Config", |mut rocket| async {
             let data = Database::load_or_create(rocket.config().await).expect("valid config");
 
+            data.sending.start_handler(&data.globals, &data.rooms);
+
             Ok(rocket.manage(data))
         }))
 }
diff --git a/src/pdu.rs b/src/pdu.rs
index c948fef45517847c35761a61e386cd8140bc232d..7118bfc8e8d6646951f2e4e43694f10cd74a9f02 100644
--- a/src/pdu.rs
+++ b/src/pdu.rs
@@ -1,22 +1,21 @@
-use crate::{Error, Result};
+use crate::Error;
 use js_int::UInt;
 use ruma::{
     events::{
-        pdu::EventHash, room::member::MemberEventContent, AnyRoomEvent, AnyStateEvent,
+        pdu::EventHash, room::member::MemberEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent,
         AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, StateEvent,
     },
-    EventId, Raw, RoomId, ServerName, UserId,
+    EventId, Raw, RoomId, ServerKeyId, ServerName, UserId,
 };
 use serde::{Deserialize, Serialize};
 use serde_json::json;
-use std::collections::HashMap;
+use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::UNIX_EPOCH};
 
-#[derive(Deserialize, Serialize)]
+#[derive(Deserialize, Serialize, Debug)]
 pub struct PduEvent {
     pub event_id: EventId,
     pub room_id: RoomId,
     pub sender: UserId,
-    pub origin: Box<ServerName>,
     pub origin_server_ts: UInt,
     #[serde(rename = "type")]
     pub kind: EventType,
@@ -31,11 +30,11 @@ pub struct PduEvent {
     #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
     pub unsigned: serde_json::Map<String, serde_json::Value>,
     pub hashes: EventHash,
-    pub signatures: HashMap<String, HashMap<String, String>>,
+    pub signatures: BTreeMap<Box<ServerName>, BTreeMap<ServerKeyId, String>>,
 }
 
 impl PduEvent {
-    pub fn redact(&mut self, reason: &PduEvent) -> Result<()> {
+    pub fn redact(&mut self, reason: &PduEvent) -> crate::Result<()> {
         self.unsigned.clear();
 
         let allowed: &[&str] = match self.kind {
@@ -101,6 +100,28 @@ pub fn to_sync_room_event(&self) -> Raw<AnySyncRoomEvent> {
         serde_json::from_value(json).expect("Raw::from_value always works")
     }
 
+    /// This only works for events that are also AnyRoomEvents.
+    pub fn to_any_event(&self) -> Raw<AnyEvent> {
+        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_room_event(&self) -> Raw<AnyRoomEvent> {
         let mut json = json!({
             "content": self.content,
@@ -177,13 +198,93 @@ pub fn to_member_event(&self) -> Raw<StateEvent<MemberEventContent>> {
 
         serde_json::from_value(json).expect("Raw::from_value always works")
     }
+
+    pub fn convert_to_outgoing_federation_event(
+        mut pdu_json: serde_json::Value,
+    ) -> Raw<ruma::events::pdu::PduStub> {
+        if let Some(unsigned) = pdu_json
+            .as_object_mut()
+            .expect("json is object")
+            .get_mut("unsigned")
+        {
+            unsigned
+                .as_object_mut()
+                .expect("unsigned is object")
+                .remove("transaction_id");
+        }
+
+        pdu_json
+            .as_object_mut()
+            .expect("json is object")
+            .remove("event_id");
+
+        serde_json::from_value::<Raw<_>>(pdu_json).expect("Raw::from_value always works")
+    }
+}
+
+impl From<&state_res::StateEvent> for PduEvent {
+    fn from(pdu: &state_res::StateEvent) -> Self {
+        Self {
+            event_id: pdu.event_id().clone(),
+            room_id: pdu.room_id().unwrap().clone(),
+            sender: pdu.sender().clone(),
+            origin_server_ts: (pdu
+                .origin_server_ts()
+                .duration_since(UNIX_EPOCH)
+                .expect("time is valid")
+                .as_millis() as u64)
+                .try_into()
+                .expect("time is valid"),
+            kind: pdu.kind(),
+            content: pdu.content().clone(),
+            state_key: Some(pdu.state_key()),
+            prev_events: pdu.prev_event_ids(),
+            depth: *pdu.depth(),
+            auth_events: pdu.auth_events(),
+            redacts: pdu.redacts().cloned(),
+            unsigned: pdu.unsigned().clone().into_iter().collect(),
+            hashes: pdu.hashes().clone(),
+            signatures: pdu.signatures(),
+        }
+    }
+}
+
+impl PduEvent {
+    pub fn convert_for_state_res(&self) -> Arc<state_res::StateEvent> {
+        Arc::new(
+            serde_json::from_value(json!({
+                "event_id": self.event_id,
+                "room_id": self.room_id,
+                "sender": self.sender,
+                "origin_server_ts": self.origin_server_ts,
+                "type": self.kind,
+                "content": self.content,
+                "state_key": self.state_key,
+                "prev_events": self.prev_events
+                    .iter()
+                    // TODO How do we create one of these
+                    .map(|id| (id, EventHash { sha256: "hello".into() }))
+                    .collect::<Vec<_>>(),
+                "depth": self.depth,
+                "auth_events": self.auth_events
+                    .iter()
+                    // TODO How do we create one of these
+                    .map(|id| (id, EventHash { sha256: "hello".into() }))
+                    .collect::<Vec<_>>(),
+                "redacts": self.redacts,
+                "unsigned": self.unsigned,
+                "hashes": self.hashes,
+                "signatures": self.signatures,
+            }))
+            .expect("all conduit PDUs are state events"),
+        )
+    }
 }
 
 /// Build the start of a PDU in order to add it to the `Database`.
-#[derive(Debug)]
+#[derive(Debug, Deserialize)]
 pub struct PduBuilder {
-    pub room_id: RoomId,
-    pub sender: UserId,
+    #[serde(rename = "type")]
     pub event_type: EventType,
     pub content: serde_json::Value,
     pub unsigned: Option<serde_json::Map<String, serde_json::Value>>,
diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs
index 8d8620435aef092d606d44b73ce3cb6eda0c6fde..734d2144ce78c494afa94535b4e03ec2f784e43d 100644
--- a/src/ruma_wrapper.rs
+++ b/src/ruma_wrapper.rs
@@ -1,6 +1,9 @@
 use crate::Error;
-use ruma::identifiers::{DeviceId, UserId};
-use std::{convert::TryInto, ops::Deref};
+use ruma::{
+    api::{Outgoing, OutgoingRequest},
+    identifiers::{DeviceId, UserId},
+};
+use std::{convert::TryFrom, convert::TryInto, ops::Deref};
 
 #[cfg(feature = "conduit_bin")]
 use {
@@ -16,21 +19,26 @@
         tokio::io::AsyncReadExt,
         Request, State,
     },
-    ruma::api::IncomingRequest,
     std::io::Cursor,
 };
 
 /// This struct converts rocket requests into ruma structs by converting them into http requests
 /// first.
-pub struct Ruma<T> {
-    pub body: T,
+pub struct Ruma<T: Outgoing> {
+    pub body: T::Incoming,
     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
 }
 
 #[cfg(feature = "conduit_bin")]
-impl<'a, T: IncomingRequest> FromTransformedData<'a> for Ruma<T> {
+impl<'a, T: Outgoing + OutgoingRequest> FromTransformedData<'a> for Ruma<T>
+where
+    <T as Outgoing>::Incoming: TryFrom<http::request::Request<std::vec::Vec<u8>>> + std::fmt::Debug,
+    <<T as Outgoing>::Incoming as std::convert::TryFrom<
+        http::request::Request<std::vec::Vec<u8>>,
+    >>::Error: std::fmt::Debug,
+{
     type Error = (); // TODO: Better error handling
     type Owned = Data;
     type Borrowed = Self::Owned;
@@ -91,7 +99,7 @@ fn from_data(
             let http_request = http_request.body(body.clone()).unwrap();
             log::info!("{:?}", http_request);
 
-            match T::try_from(http_request) {
+            match <T as Outgoing>::Incoming::try_from(http_request) {
                 Ok(t) => Success(Ruma {
                     body: t,
                     sender_id: user_id,
@@ -110,8 +118,8 @@ fn from_data(
     }
 }
 
-impl<T> Deref for Ruma<T> {
-    type Target = T;
+impl<T: Outgoing> Deref for Ruma<T> {
+    type Target = T::Incoming;
 
     fn deref(&self) -> &Self::Target {
         &self.body
diff --git a/src/server_server.rs b/src/server_server.rs
index f48f502b946067a6d9e04d773527a82d1e45467c..b8b575ebef6f888742a20074b10bd4db332e980b 100644
--- a/src/server_server.rs
+++ b/src/server_server.rs
@@ -1,25 +1,38 @@
-use crate::{client_server, ConduitResult, Database, Error, Result, Ruma};
-use http::header::{HeaderValue, AUTHORIZATION};
+use crate::{client_server, ConduitResult, Database, Error, PduEvent, Result, Ruma};
+use get_profile_information::v1::ProfileField;
+use http::header::{HeaderValue, AUTHORIZATION, HOST};
+use log::warn;
 use rocket::{get, post, put, response::content::Json, State};
-use ruma::api::federation::{
-    directory::get_public_rooms,
-    discovery::{
-        get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
+use ruma::{
+    api::{
+        federation::{
+            directory::{get_public_rooms, get_public_rooms_filtered},
+            discovery::{
+                get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
+            },
+            event::get_missing_events,
+            query::get_profile_information,
+            transactions::send_transaction_message,
+        },
+        OutgoingRequest,
     },
-    transactions::send_transaction_message,
+    directory::{IncomingFilter, IncomingRoomNetwork},
+    EventId, ServerName,
 };
-use ruma::api::{client, OutgoingRequest};
-use serde_json::json;
 use std::{
     collections::BTreeMap,
     convert::TryFrom,
     fmt::Debug,
     time::{Duration, SystemTime},
 };
+use trust_dns_resolver::AsyncResolver;
 
-pub async fn request_well_known(db: &crate::Database, destination: &str) -> Option<String> {
+pub async fn request_well_known(
+    globals: &crate::database::globals::Globals,
+    destination: &str,
+) -> Option<String> {
     let body: serde_json::Value = serde_json::from_str(
-        &db.globals
+        &globals
             .reqwest_client()
             .get(&format!(
                 "https://{}/.well-known/matrix/server",
@@ -37,28 +50,62 @@ pub async fn request_well_known(db: &crate::Database, destination: &str) -> Opti
 }
 
 pub async fn send_request<T: OutgoingRequest>(
-    db: &crate::Database,
-    destination: String,
+    globals: &crate::database::globals::Globals,
+    destination: Box<ServerName>,
     request: T,
 ) -> Result<T::IncomingResponse>
 where
     T: Debug,
 {
+    if !globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let resolver = AsyncResolver::tokio_from_system_conf()
+        .await
+        .map_err(|_| Error::BadConfig("Failed to set up trust dns resolver with system config."))?;
+
+    let mut host = None;
+
     let actual_destination = "https://".to_owned()
-        + &request_well_known(db, &destination)
-            .await
-            .unwrap_or(destination.clone() + ":8448");
+        + &if let Some(mut delegated_hostname) =
+            request_well_known(globals, &destination.as_str()).await
+        {
+            if let Ok(Some(srv)) = resolver
+                .srv_lookup(format!("_matrix._tcp.{}", delegated_hostname))
+                .await
+                .map(|srv| srv.iter().next().map(|result| result.target().to_string()))
+            {
+                host = Some(delegated_hostname);
+                srv.trim_end_matches('.').to_owned()
+            } else {
+                if delegated_hostname.find(':').is_none() {
+                    delegated_hostname += ":8448";
+                }
+                delegated_hostname
+            }
+        } else {
+            let mut destination = destination.as_str().to_owned();
+            if destination.find(':').is_none() {
+                destination += ":8448";
+            }
+            destination
+        };
 
     let mut http_request = request
         .try_into_http_request(&actual_destination, Some(""))
-        .unwrap();
+        .map_err(|e| {
+            warn!("{}: {}", actual_destination, e);
+            Error::BadServerResponse("Invalid destination")
+        })?;
 
     let mut request_map = serde_json::Map::new();
 
     if !http_request.body().is_empty() {
         request_map.insert(
             "content".to_owned(),
-            serde_json::from_slice(http_request.body()).unwrap(),
+            serde_json::from_slice(http_request.body())
+                .expect("body is valid json, we just created it"),
         );
     };
 
@@ -72,19 +119,16 @@ pub async fn send_request<T: OutgoingRequest>(
             .to_string()
             .into(),
     );
-    request_map.insert(
-        "origin".to_owned(),
-        db.globals.server_name().as_str().into(),
-    );
-    request_map.insert("destination".to_owned(), destination.into());
+    request_map.insert("origin".to_owned(), globals.server_name().as_str().into());
+    request_map.insert("destination".to_owned(), destination.as_str().into());
 
     let mut request_json = request_map.into();
     ruma::signatures::sign_json(
-        db.globals.server_name().as_str(),
-        db.globals.keypair(),
+        globals.server_name().as_str(),
+        globals.keypair(),
         &mut request_json,
     )
-    .unwrap();
+    .expect("our request json is what ruma expects");
 
     let signatures = request_json["signatures"]
         .as_object()
@@ -103,7 +147,7 @@ pub async fn send_request<T: OutgoingRequest>(
                 AUTHORIZATION,
                 HeaderValue::from_str(&format!(
                     "X-Matrix origin={},key=\"{}\",sig=\"{}\"",
-                    db.globals.server_name(),
+                    globals.server_name(),
                     s.0,
                     s.1
                 ))
@@ -112,10 +156,19 @@ pub async fn send_request<T: OutgoingRequest>(
         }
     }
 
-    let reqwest_request = reqwest::Request::try_from(http_request)
+    if let Some(host) = host {
+        http_request
+            .headers_mut()
+            .insert(HOST, HeaderValue::from_str(&host).unwrap());
+    }
+
+    let mut reqwest_request = reqwest::Request::try_from(http_request)
         .expect("all http requests are valid reqwest requests");
 
-    let reqwest_response = db.globals.reqwest_client().execute(reqwest_request).await;
+    *reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
+
+    let url = reqwest_request.url().clone();
+    let reqwest_response = globals.reqwest_client().execute(reqwest_request).await;
 
     // Because reqwest::Response -> http::Response is complicated:
     match reqwest_response {
@@ -136,22 +189,30 @@ pub async fn send_request<T: OutgoingRequest>(
                 .unwrap()
                 .into_iter()
                 .collect();
-            Ok(
-                T::IncomingResponse::try_from(http_response.body(body).unwrap())
-                    .expect("TODO: error handle other server errors"),
-            )
+
+            let response = T::IncomingResponse::try_from(
+                http_response
+                    .body(body)
+                    .expect("reqwest body is valid http body"),
+            );
+            response.map_err(|e| {
+                warn!(
+                    "Server returned bad response {} ({}): {:?}",
+                    destination, url, e
+                );
+                Error::BadServerResponse("Server returned bad response.")
+            })
         }
         Err(e) => Err(e.into()),
     }
 }
 
-#[cfg_attr(feature = "conduit_bin", get("/.well-known/matrix/server"))]
-pub fn well_known_server() -> Json<String> {
-    rocket::response::content::Json(json!({ "m.server": "pc.koesters.xyz:59003"}).to_string())
-}
-
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/federation/v1/version"))]
-pub fn get_server_version() -> ConduitResult<get_server_version::Response> {
+pub fn get_server_version(db: State<'_, Database>) -> ConduitResult<get_server_version::Response> {
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
     Ok(get_server_version::Response {
         server: Some(get_server_version::Server {
             name: Some("Conduit".to_owned()),
@@ -163,6 +224,11 @@ pub fn get_server_version() -> ConduitResult<get_server_version::Response> {
 
 #[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server"))]
 pub fn get_server_keys(db: State<'_, Database>) -> Json<String> {
+    if !db.globals.federation_enabled() {
+        // TODO: Use proper types
+        return Json("Federation is disabled.".to_owned());
+    }
+
     let mut verify_keys = BTreeMap::new();
     verify_keys.insert(
         format!("ed25519:{}", db.globals.keypair().version()),
@@ -202,47 +268,75 @@ pub fn get_server_keys_deprecated(db: State<'_, Database>) -> Json<String> {
     feature = "conduit_bin",
     post("/_matrix/federation/v1/publicRooms", data = "<body>")
 )]
+pub async fn get_public_rooms_filtered_route(
+    db: State<'_, Database>,
+    body: Ruma<get_public_rooms_filtered::v1::Request<'_>>,
+) -> ConduitResult<get_public_rooms_filtered::v1::Response> {
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let response = client_server::get_public_rooms_filtered_helper(
+        &db,
+        None,
+        body.limit,
+        body.since.as_deref(),
+        &body.filter,
+        &body.room_network,
+    )
+    .await?
+    .0;
+
+    Ok(get_public_rooms_filtered::v1::Response {
+        chunk: response
+            .chunk
+            .into_iter()
+            .map(|c| {
+                // Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
+                // to ruma::api::client::r0::directory::PublicRoomsChunk
+                Ok::<_, Error>(
+                    serde_json::from_str(
+                        &serde_json::to_string(&c)
+                            .expect("PublicRoomsChunk::to_string always works"),
+                    )
+                    .expect("federation and client-server PublicRoomsChunk are the same type"),
+                )
+            })
+            .filter_map(|r| r.ok())
+            .collect(),
+        prev_batch: response.prev_batch,
+        next_batch: response.next_batch,
+        total_room_count_estimate: response.total_room_count_estimate,
+    }
+    .into())
+}
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/federation/v1/publicRooms", data = "<body>")
+)]
 pub async fn get_public_rooms_route(
     db: State<'_, Database>,
-    body: Ruma<get_public_rooms::v1::Request>,
+    body: Ruma<get_public_rooms::v1::Request<'_>>,
 ) -> ConduitResult<get_public_rooms::v1::Response> {
-    let Ruma {
-        body:
-            get_public_rooms::v1::Request {
-                room_network: _room_network, // TODO
-                limit,
-                since,
-            },
-        sender_id,
-        device_id,
-        json_body,
-    } = body;
-
-    let client::r0::directory::get_public_rooms_filtered::Response {
-        chunk,
-        prev_batch,
-        next_batch,
-        total_room_count_estimate,
-    } = client_server::get_public_rooms_filtered_route(
-        db,
-        Ruma {
-            body: client::r0::directory::get_public_rooms_filtered::IncomingRequest {
-                filter: None,
-                limit,
-                room_network: client::r0::directory::get_public_rooms_filtered::RoomNetwork::Matrix,
-                server: None,
-                since,
-            },
-            sender_id,
-            device_id,
-            json_body,
-        },
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let response = client_server::get_public_rooms_filtered_helper(
+        &db,
+        None,
+        body.limit,
+        body.since.as_deref(),
+        &IncomingFilter::default(),
+        &IncomingRoomNetwork::Matrix,
     )
     .await?
     .0;
 
     Ok(get_public_rooms::v1::Response {
-        chunk: chunk
+        chunk: response
+            .chunk
             .into_iter()
             .map(|c| {
                 // Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
@@ -257,9 +351,9 @@ pub async fn get_public_rooms_route(
             })
             .filter_map(|r| r.ok())
             .collect(),
-        prev_batch,
-        next_batch,
-        total_room_count_estimate,
+        prev_batch: response.prev_batch,
+        next_batch: response.next_batch,
+        total_room_count_estimate: response.total_room_count_estimate,
     }
     .into())
 }
@@ -268,13 +362,150 @@ pub async fn get_public_rooms_route(
     feature = "conduit_bin",
     put("/_matrix/federation/v1/send/<_>", data = "<body>")
 )]
-pub fn send_transaction_message_route(
-    db: State<'_, Database>,
-    body: Ruma<send_transaction_message::v1::Request>,
+pub fn send_transaction_message_route<'a>(
+    db: State<'a, Database>,
+    body: Ruma<send_transaction_message::v1::Request<'_>>,
 ) -> ConduitResult<send_transaction_message::v1::Response> {
-    dbg!(&*body);
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    //dbg!(&*body);
+    for pdu in &body.pdus {
+        let mut value = serde_json::from_str(pdu.json().get())
+            .expect("converting raw jsons to values always works");
+
+        let event_id = EventId::try_from(&*format!(
+            "${}",
+            ruma::signatures::reference_hash(&value).expect("ruma can calculate reference hashes")
+        ))
+        .expect("ruma's reference hashes are valid event ids");
+
+        value
+            .as_object_mut()
+            .expect("ruma pdus are json objects")
+            .insert("event_id".to_owned(), event_id.to_string().into());
+
+        let pdu = serde_json::from_value::<PduEvent>(value.clone())
+            .expect("all ruma pdus are conduit pdus");
+        if db.rooms.exists(&pdu.room_id)? {
+            let pdu_id =
+                db.rooms
+                    .append_pdu(&pdu, &value, &db.globals, &db.account_data, &db.sending)?;
+            db.rooms.append_to_state(&pdu_id, &pdu)?;
+        }
+    }
     Ok(send_transaction_message::v1::Response {
         pdus: BTreeMap::new(),
     }
     .into())
 }
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    post("/_matrix/federation/v1/get_missing_events/<_>", data = "<body>")
+)]
+pub fn get_missing_events_route<'a>(
+    db: State<'a, Database>,
+    body: Ruma<get_missing_events::v1::Request<'_>>,
+) -> ConduitResult<get_missing_events::v1::Response> {
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let mut queued_events = body.latest_events.clone();
+    let mut events = Vec::new();
+
+    let mut i = 0;
+    while i < queued_events.len() && events.len() < u64::from(body.limit) as usize {
+        if let Some(pdu) = db.rooms.get_pdu_json(&queued_events[i])? {
+            if body.earliest_events.contains(
+                &serde_json::from_value(
+                    pdu.get("event_id")
+                        .cloned()
+                        .ok_or_else(|| Error::bad_database("Event in db has no event_id field."))?,
+                )
+                .map_err(|_| Error::bad_database("Invalid event_id field in pdu in db."))?,
+            ) {
+                i += 1;
+                continue;
+            }
+            queued_events.extend_from_slice(
+                &serde_json::from_value::<Vec<EventId>>(
+                    pdu.get("prev_events").cloned().ok_or_else(|| {
+                        Error::bad_database("Invalid prev_events field of pdu in db.")
+                    })?,
+                )
+                .map_err(|_| Error::bad_database("Invalid prev_events content in pdu in db."))?,
+            );
+            events.push(PduEvent::convert_to_outgoing_federation_event(pdu));
+        }
+        i += 1;
+    }
+
+    Ok(get_missing_events::v1::Response { events }.into())
+}
+
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/federation/v1/query/profile", data = "<body>")
+)]
+pub fn get_profile_information_route<'a>(
+    db: State<'a, Database>,
+    body: Ruma<get_profile_information::v1::Request<'_>>,
+) -> ConduitResult<get_profile_information::v1::Response> {
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let mut displayname = None;
+    let mut avatar_url = None;
+
+    match body.field {
+        Some(ProfileField::DisplayName) => displayname = db.users.displayname(&body.user_id)?,
+        Some(ProfileField::AvatarUrl) => avatar_url = db.users.avatar_url(&body.user_id)?,
+        None => {
+            displayname = db.users.displayname(&body.user_id)?;
+            avatar_url = db.users.avatar_url(&body.user_id)?;
+        }
+    }
+
+    Ok(get_profile_information::v1::Response {
+        displayname,
+        avatar_url,
+    }
+    .into())
+}
+
+/*
+#[cfg_attr(
+    feature = "conduit_bin",
+    get("/_matrix/federation/v2/invite/<_>/<_>", data = "<body>")
+)]
+pub fn get_user_devices_route<'a>(
+    db: State<'a, Database>,
+    body: Ruma<membership::v1::Request<'_>>,
+) -> ConduitResult<get_profile_information::v1::Response> {
+    if !db.globals.federation_enabled() {
+        return Err(Error::BadConfig("Federation is disabled."));
+    }
+
+    let mut displayname = None;
+    let mut avatar_url = None;
+
+    match body.field {
+        Some(ProfileField::DisplayName) => displayname = db.users.displayname(&body.user_id)?,
+        Some(ProfileField::AvatarUrl) => avatar_url = db.users.avatar_url(&body.user_id)?,
+        None => {
+            displayname = db.users.displayname(&body.user_id)?;
+            avatar_url = db.users.avatar_url(&body.user_id)?;
+        }
+    }
+
+    Ok(get_profile_information::v1::Response {
+        displayname,
+        avatar_url,
+    }
+    .into())
+}
+*/
diff --git a/src/stateres.rs b/src/stateres.rs
deleted file mode 100644
index ee47099a2ff697bfe3430807c9a60c796d8d0a15..0000000000000000000000000000000000000000
--- a/src/stateres.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use std::collections::HashMap;
-
-fn stateres(state_a: HashMap<StateTuple, PduEvent>, state_b: HashMap<StateTuple, PduEvent>) {
-    let mut unconflicted = todo!("state at fork event");
-
-    let mut conflicted: HashMap<StateTuple, PduEvent> = state_a
-        .iter()
-        .filter(|(key_a, value_a)| match state_b.remove(key_a) {
-            Some(value_b) if value_a == value_b => unconflicted.insert(key_a, value_a),
-            _ => false,
-        })
-        .collect();
-
-    // We removed unconflicted from state_b, now we can easily insert all events that are only in fork b
-    conflicted.extend(state_b);
-
-    let partial_state = unconflicted.clone();
-
-    let full_conflicted = conflicted.clone(); // TODO: auth events
-
-    let output_rev = Vec::new();
-    let event_map = HashMap::new();
-    let incoming_edges = HashMap::new();
-
-    for event in full_conflicted {
-        event_map.insert(event.event_id, event);
-        incoming_edges.insert(event.event_id, 0);
-    }
-
-    for e in conflicted_control_events {
-        for a in e.auth_events {
-            incoming_edges[a.event_id] += 1;
-        }
-    }
-
-    while incoming_edges.len() > 0 {
-        let mut count_0 = incoming_edges
-            .iter()
-            .filter(|(_, c)| c == 0)
-            .collect::<Vec<_>>();
-
-        count_0.sort_by(|(x, _), (y, _)| {
-            x.power_level
-                .cmp(&a.power_level)
-                .then_with(|| x.origin_server.ts.cmp(&y.origin_server_ts))
-                .then_with(|| x.event_id.cmp(&y.event_id))
-        });
-
-        for (id, count) in count_0 {
-            output_rev.push(event_map[id]);
-
-            for auth_event in event_map[id].auth_events {
-                incoming_edges[auth_event.event_id] -= 1;
-            }
-
-            incoming_edges.remove(id);
-        }
-    }
-}
diff --git a/src/utils.rs b/src/utils.rs
index 8cf1b2ce1795b95dbd115693f6424d1995fede2c..452b7c5afea2bbf2f2c7ba3232039cd1f24969eb 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -29,8 +29,13 @@ pub fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> {
 
 pub fn generate_keypair(old: Option<&[u8]>) -> Option<Vec<u8>> {
     Some(old.map(|s| s.to_vec()).unwrap_or_else(|| {
-        ruma::signatures::Ed25519KeyPair::generate()
-            .expect("Ed25519KeyPair generation always works (?)")
+        let mut value = random_string(8).as_bytes().to_vec();
+        value.push(0xff);
+        value.extend_from_slice(
+            &ruma::signatures::Ed25519KeyPair::generate()
+                .expect("Ed25519KeyPair generation always works (?)"),
+        );
+        value
     }))
 }