From e4f769963fb880d6131ae940f89ab9b1193c5d32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Thu, 6 Jul 2023 10:32:25 +0200
Subject: [PATCH 1/6] feat: very simple sliding sync implementation

---
 Cargo.lock                              |  22 +-
 Cargo.toml                              |   6 +-
 README.md                               |   2 +-
 src/api/client_server/directory.rs      |  12 +-
 src/api/client_server/sync.rs           | 270 ++++++++++++++++++++----
 src/api/client_server/unversioned.rs    |  20 +-
 src/config/mod.rs                       |   1 +
 src/main.rs                             |   1 +
 src/service/globals/mod.rs              |   4 +
 src/service/pusher/mod.rs               |  16 +-
 src/service/rooms/spaces/mod.rs         |  10 +-
 src/service/rooms/state_accessor/mod.rs |  13 ++
 12 files changed, 284 insertions(+), 93 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 41483941..487780dd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2111,7 +2111,7 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.8.2"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "assign",
  "js_int",
@@ -2129,7 +2129,7 @@ dependencies = [
 [[package]]
 name = "ruma-appservice-api"
 version = "0.8.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2140,7 +2140,7 @@ dependencies = [
 [[package]]
 name = "ruma-client-api"
 version = "0.16.2"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "assign",
  "bytes",
@@ -2157,7 +2157,7 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.11.3"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "base64 0.21.2",
  "bytes",
@@ -2185,7 +2185,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.7.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2196,7 +2196,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-validation"
 version = "0.9.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "js_int",
  "thiserror",
@@ -2205,7 +2205,7 @@ dependencies = [
 [[package]]
 name = "ruma-identity-service-api"
 version = "0.7.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2215,7 +2215,7 @@ dependencies = [
 [[package]]
 name = "ruma-macros"
 version = "0.11.3"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "once_cell",
  "proc-macro-crate",
@@ -2230,7 +2230,7 @@ dependencies = [
 [[package]]
 name = "ruma-push-gateway-api"
 version = "0.7.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2241,7 +2241,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.13.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "base64 0.21.2",
  "ed25519-dalek",
@@ -2257,7 +2257,7 @@ dependencies = [
 [[package]]
 name = "ruma-state-res"
 version = "0.9.1"
-source = "git+https://github.com/ruma/ruma?rev=38294bd5206498c02b1001227d65654eb548308b#38294bd5206498c02b1001227d65654eb548308b"
+source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
 dependencies = [
  "itertools",
  "js_int",
diff --git a/Cargo.toml b/Cargo.toml
index bc29c5a0..a01f410b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,9 +26,9 @@ tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitiv
 
 # Used for matrix spec type definitions and helpers
 #ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
-ruma = { git = "https://github.com/ruma/ruma", rev = "38294bd5206498c02b1001227d65654eb548308b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
-#ruma = { git = "https://github.com/timokoesters/ruma", rev = "50c1db7e0a3a21fc794b0cce3b64285a4c750c71", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
-#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
+#ruma = { git = "https://github.com/ruma/ruma", rev = "38294bd5206498c02b1001227d65654eb548308b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
+ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
+#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
 
 # Async runtime and utilities
 tokio = { version = "1.28.1", features = ["fs", "macros", "signal", "sync"] }
diff --git a/README.md b/README.md
index f73f6aa9..8fabefd6 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ Thanks to the contributors to Conduit and all libraries we use, for example:
 If you run into any question, feel free to
 - Ask us in `#conduit:fachschaften.org` on Matrix
 - Write an E-Mail to `conduit@koesters.xyz`
-- Send an direct message to `timo@fachschaften.org` on Matrix
+- Send an direct message to `timokoesters@fachschaften.org` on Matrix
 - [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
 
 #### Donate
diff --git a/src/api/client_server/directory.rs b/src/api/client_server/directory.rs
index e1322109..df1ac40c 100644
--- a/src/api/client_server/directory.rs
+++ b/src/api/client_server/directory.rs
@@ -203,17 +203,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
                                 Error::bad_database("Invalid canonical alias event in database.")
                             })
                     })?,
-                name: services()
-                    .rooms
-                    .state_accessor
-                    .room_state_get(&room_id, &StateEventType::RoomName, "")?
-                    .map_or(Ok(None), |s| {
-                        serde_json::from_str(s.content.get())
-                            .map(|c: RoomNameEventContent| c.name)
-                            .map_err(|_| {
-                                Error::bad_database("Invalid room name event in database.")
-                            })
-                    })?,
+                name: services().rooms.state_accessor.get_name(&room_id)?,
                 num_joined_members: services()
                     .rooms
                     .state_cache
diff --git a/src/api/client_server/sync.rs b/src/api/client_server/sync.rs
index dd753470..bc89a4c3 100644
--- a/src/api/client_server/sync.rs
+++ b/src/api/client_server/sync.rs
@@ -1,4 +1,6 @@
-use crate::{service::rooms::timeline::PduCount, services, Error, Result, Ruma, RumaResponse};
+use crate::{
+    service::rooms::timeline::PduCount, services, Error, PduEvent, Result, Ruma, RumaResponse,
+};
 use ruma::{
     api::client::{
         filter::{FilterDefinition, LazyLoadOptions},
@@ -8,6 +10,7 @@ use ruma::{
                 Ephemeral, Filter, GlobalAccountData, InviteState, InvitedRoom, JoinedRoom,
                 LeftRoom, Presence, RoomAccountData, RoomSummary, Rooms, State, Timeline, ToDevice,
             },
+            v4::SlidingOp,
             DeviceLists, UnreadNotificationsCount,
         },
         uiaa::UiaaResponse,
@@ -17,10 +20,10 @@ use ruma::{
         StateEventType, TimelineEventType,
     },
     serde::Raw,
-    DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UserId,
+    uint, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId,
 };
 use std::{
-    collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
+    collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
     sync::Arc,
     time::Duration,
 };
@@ -199,7 +202,7 @@ async fn sync_helper(
     let mut joined_rooms = BTreeMap::new();
     let since = body
         .since
-        .clone()
+        .as_ref()
         .and_then(|string| string.parse().ok())
         .unwrap_or(0);
     let sincecount = PduCount::Normal(since);
@@ -581,43 +584,7 @@ async fn load_joined_room(
         drop(insert_lock);
     }
 
-    let timeline_pdus;
-    let limited;
-    if services()
-        .rooms
-        .timeline
-        .last_timeline_count(&sender_user, &room_id)?
-        > sincecount
-    {
-        let mut non_timeline_pdus = services()
-            .rooms
-            .timeline
-            .pdus_until(&sender_user, &room_id, PduCount::max())?
-            .filter_map(|r| {
-                // Filter out buggy events
-                if r.is_err() {
-                    error!("Bad pdu in pdus_since: {:?}", r);
-                }
-                r.ok()
-            })
-            .take_while(|(pducount, _)| pducount > &sincecount);
-
-        // Take the last 10 events for the timeline
-        timeline_pdus = non_timeline_pdus
-            .by_ref()
-            .take(10)
-            .collect::<Vec<_>>()
-            .into_iter()
-            .rev()
-            .collect::<Vec<_>>();
-
-        // They /sync response doesn't always return all messages, so we say the output is
-        // limited unless there are events in non_timeline_pdus
-        limited = non_timeline_pdus.next().is_some();
-    } else {
-        timeline_pdus = Vec::new();
-        limited = false;
-    }
+    let (timeline_pdus, limited) = load_timeline(sender_user, room_id, sincecount, 10)?;
 
     let send_notification_counts = !timeline_pdus.is_empty()
         || services()
@@ -1132,6 +1099,52 @@ async fn load_joined_room(
     })
 }
 
+fn load_timeline(
+    sender_user: &UserId,
+    room_id: &RoomId,
+    sincecount: PduCount,
+    limit: u64,
+) -> Result<(Vec<(PduCount, PduEvent)>, bool), Error> {
+    let timeline_pdus;
+    let limited;
+    if services()
+        .rooms
+        .timeline
+        .last_timeline_count(&sender_user, &room_id)?
+        > sincecount
+    {
+        let mut non_timeline_pdus = services()
+            .rooms
+            .timeline
+            .pdus_until(&sender_user, &room_id, PduCount::max())?
+            .filter_map(|r| {
+                // Filter out buggy events
+                if r.is_err() {
+                    error!("Bad pdu in pdus_since: {:?}", r);
+                }
+                r.ok()
+            })
+            .take_while(|(pducount, _)| pducount > &sincecount);
+
+        // Take the last events for the timeline
+        timeline_pdus = non_timeline_pdus
+            .by_ref()
+            .take(limit as usize)
+            .collect::<Vec<_>>()
+            .into_iter()
+            .rev()
+            .collect::<Vec<_>>();
+
+        // They /sync response doesn't always return all messages, so we say the output is
+        // limited unless there are events in non_timeline_pdus
+        limited = non_timeline_pdus.next().is_some();
+    } else {
+        timeline_pdus = Vec::new();
+        limited = false;
+    }
+    Ok((timeline_pdus, limited))
+}
+
 fn share_encrypted_room(
     sender_user: &UserId,
     user_id: &UserId,
@@ -1155,3 +1168,178 @@ fn share_encrypted_room(
         })
         .any(|encrypted| encrypted))
 }
+
+pub async fn sync_events_v4_route(
+    body: Ruma<sync_events::v4::Request>,
+) -> Result<sync_events::v4::Response, RumaResponse<UiaaResponse>> {
+    let sender_user = body.sender_user.expect("user is authenticated");
+    let sender_device = body.sender_device.expect("user is authenticated");
+    let body = dbg!(body.body);
+
+    // Setup watchers, so if there's no response, we can wait for them
+    let watcher = services().globals.watch(&sender_user, &sender_device);
+
+    let next_batch = services().globals.current_count()?;
+
+    let since = body
+        .pos
+        .as_ref()
+        .and_then(|string| string.parse().ok())
+        .unwrap_or(0);
+    let sincecount = PduCount::Normal(since);
+
+    let initial = since == 0;
+
+    let all_joined_rooms = services()
+        .rooms
+        .state_cache
+        .rooms_joined(&sender_user)
+        .filter_map(|r| r.ok())
+        .collect::<Vec<_>>();
+
+    let mut lists = BTreeMap::new();
+    let mut todo_rooms = BTreeMap::new(); // and required state
+
+    for (list_id, list) in body.lists {
+        if list.filters.and_then(|f| f.is_invite).unwrap_or(false) {
+            continue;
+        }
+
+        lists.insert(
+            list_id,
+            sync_events::v4::SyncList {
+                ops: list
+                    .ranges
+                    .into_iter()
+                    .map(|mut r| {
+                        r.0 =
+                            r.0.clamp(uint!(0), UInt::from(all_joined_rooms.len() as u32 - 1));
+                        r.1 =
+                            r.1.clamp(r.0, UInt::from(all_joined_rooms.len() as u32 - 1));
+                        let room_ids = all_joined_rooms
+                            [(u64::from(r.0) as usize)..=(u64::from(r.1) as usize)]
+                            .to_vec();
+                        todo_rooms.extend(room_ids.iter().cloned().map(|r| {
+                            let limit = list
+                                .room_details
+                                .timeline_limit
+                                .map_or(10, u64::from)
+                                .min(100);
+                            (r, (list.room_details.required_state.clone(), limit))
+                        }));
+                        sync_events::v4::SyncOp {
+                            op: SlidingOp::Sync,
+                            range: Some(r.clone()),
+                            index: None,
+                            room_ids,
+                            room_id: None,
+                        }
+                    })
+                    .collect(),
+                count: UInt::from(all_joined_rooms.len() as u32),
+            },
+        );
+    }
+
+    let mut rooms = BTreeMap::new();
+    for (room_id, (required_state_request, timeline_limit)) in todo_rooms {
+        let (timeline_pdus, limited) =
+            load_timeline(&sender_user, &room_id, sincecount, timeline_limit)?;
+
+        let room_events: Vec<_> = timeline_pdus
+            .iter()
+            .map(|(_, pdu)| pdu.to_sync_room_event())
+            .collect();
+
+        let required_state = required_state_request
+            .iter()
+            .map(|state| {
+                services()
+                    .rooms
+                    .state_accessor
+                    .room_state_get(&room_id, &state.0, &state.1)
+            })
+            .filter_map(|r| r.ok())
+            .filter_map(|o| o)
+            .map(|state| state.to_sync_state_event())
+            .collect();
+
+        rooms.insert(
+            room_id.clone(),
+            sync_events::v4::SlidingSyncRoom {
+                name: services().rooms.state_accessor.get_name(&room_id)?,
+                initial: Some(initial),
+                is_dm: None,
+                invite_state: None,
+                unread_notifications: UnreadNotificationsCount {
+                    highlight_count: None,
+                    notification_count: None,
+                },
+                timeline: room_events,
+                required_state,
+                prev_batch: None,
+                limited,
+                joined_count: Some(
+                    (services()
+                        .rooms
+                        .state_cache
+                        .room_joined_count(&room_id)?
+                        .unwrap_or(0) as u32)
+                        .into(),
+                ),
+                invited_count: Some(
+                    (services()
+                        .rooms
+                        .state_cache
+                        .room_invited_count(&room_id)?
+                        .unwrap_or(0) as u32)
+                        .into(),
+                ),
+                num_live: None,
+            },
+        );
+    }
+
+    if rooms
+        .iter()
+        .all(|(_, r)| r.timeline.is_empty() && r.required_state.is_empty())
+    {
+        // Hang a few seconds so requests are not spammed
+        // Stop hanging if new info arrives
+        let mut duration = body.timeout.unwrap_or(Duration::from_secs(30));
+        if duration.as_secs() > 30 {
+            duration = Duration::from_secs(30);
+        }
+        let _ = tokio::time::timeout(duration, watcher).await;
+    }
+
+    Ok(dbg!(sync_events::v4::Response {
+        initial: initial,
+        txn_id: body.txn_id.clone(),
+        pos: next_batch.to_string(),
+        lists,
+        rooms,
+        extensions: sync_events::v4::Extensions {
+            to_device: None,
+            e2ee: sync_events::v4::E2EE {
+                device_lists: DeviceLists {
+                    changed: Vec::new(),
+                    left: Vec::new(),
+                },
+                device_one_time_keys_count: BTreeMap::new(),
+                device_unused_fallback_key_types: None,
+            },
+            account_data: sync_events::v4::AccountData {
+                global: Vec::new(),
+                rooms: BTreeMap::new(),
+            },
+            receipts: sync_events::v4::Receipts {
+                rooms: BTreeMap::new(),
+            },
+            typing: sync_events::v4::Typing {
+                rooms: BTreeMap::new(),
+            },
+        },
+        delta_token: None,
+    }))
+}
diff --git a/src/api/client_server/unversioned.rs b/src/api/client_server/unversioned.rs
index b4f03f41..797b9529 100644
--- a/src/api/client_server/unversioned.rs
+++ b/src/api/client_server/unversioned.rs
@@ -1,8 +1,9 @@
 use std::{collections::BTreeMap, iter::FromIterator};
 
-use ruma::api::client::discovery::get_supported_versions;
+use axum::{response::IntoResponse, Json};
+use ruma::api::client::{discovery::get_supported_versions, error::ErrorKind};
 
-use crate::{Result, Ruma};
+use crate::{services, Error, Result, Ruma};
 
 /// # `GET /_matrix/client/versions`
 ///
@@ -31,3 +32,18 @@ pub async fn get_supported_versions_route(
 
     Ok(resp)
 }
+
+/// # `GET /.well-known/matrix/client`
+pub async fn well_known_client_route(
+    _body: Ruma<get_supported_versions::Request>,
+) -> Result<impl IntoResponse> {
+    let client_url = match services().globals.well_known_client() {
+        Some(url) => url.clone(),
+        None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
+    };
+
+    Ok(Json(serde_json::json!({
+        "m.homeserver": {"base_url": client_url},
+        "org.matrix.msc3575.proxy": {"url": client_url}
+    })))
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f9222825..4dad9f79 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -54,6 +54,7 @@ pub struct Config {
     pub allow_unstable_room_versions: bool,
     #[serde(default = "default_default_room_version")]
     pub default_room_version: RoomVersionId,
+    pub well_known_client: Option<String>,
     #[serde(default = "false_fn")]
     pub allow_jaeger: bool,
     #[serde(default = "false_fn")]
diff --git a/src/main.rs b/src/main.rs
index ea5572ee..579eeb15 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -357,6 +357,7 @@ fn routes() -> Router {
                 .put(client_server::send_state_event_for_empty_key_route),
         )
         .ruma_route(client_server::sync_events_route)
+        .ruma_route(client_server::sync_events_v4_route)
         .ruma_route(client_server::get_context_route)
         .ruma_route(client_server::get_message_events_route)
         .ruma_route(client_server::search_events_route)
diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs
index e4affde2..5326b7a9 100644
--- a/src/service/globals/mod.rs
+++ b/src/service/globals/mod.rs
@@ -342,6 +342,10 @@ impl Service {
         r
     }
 
+    pub fn well_known_client(&self) -> &Option<String> {
+        &self.config.well_known_client
+    }
+
     pub fn shutdown(&self) {
         self.shutdown.store(true, atomic::Ordering::Relaxed);
         // On shutdown
diff --git a/src/service/pusher/mod.rs b/src/service/pusher/mod.rs
index d4acaa59..5e4281d2 100644
--- a/src/service/pusher/mod.rs
+++ b/src/service/pusher/mod.rs
@@ -270,21 +270,7 @@ impl Service {
 
                     notifi.sender_display_name = services().users.displayname(&event.sender)?;
 
-                    let room_name = if let Some(room_name_pdu) = services()
-                        .rooms
-                        .state_accessor
-                        .room_state_get(&event.room_id, &StateEventType::RoomName, "")?
-                    {
-                        serde_json::from_str::<RoomNameEventContent>(room_name_pdu.content.get())
-                            .map_err(|_| {
-                                Error::bad_database("Invalid room name event in database.")
-                            })?
-                            .name
-                    } else {
-                        None
-                    };
-
-                    notifi.room_name = room_name;
+                    notifi.room_name = services().rooms.state_accessor.get_name(&event.room_id)?;
 
                     self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
                         .await?;
diff --git a/src/service/rooms/spaces/mod.rs b/src/service/rooms/spaces/mod.rs
index 76ba6c57..380f86cf 100644
--- a/src/service/rooms/spaces/mod.rs
+++ b/src/service/rooms/spaces/mod.rs
@@ -273,15 +273,7 @@ impl Service {
                             Error::bad_database("Invalid canonical alias event in database.")
                         })
                 })?,
-            name: services()
-                .rooms
-                .state_accessor
-                .room_state_get(&room_id, &StateEventType::RoomName, "")?
-                .map_or(Ok(None), |s| {
-                    serde_json::from_str(s.content.get())
-                        .map(|c: RoomNameEventContent| c.name)
-                        .map_err(|_| Error::bad_database("Invalid room name event in database."))
-                })?,
+            name: services().rooms.state_accessor.get_name(&room_id)?,
             num_joined_members: services()
                 .rooms
                 .state_cache
diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs
index a25a8b5d..9d071a53 100644
--- a/src/service/rooms/state_accessor/mod.rs
+++ b/src/service/rooms/state_accessor/mod.rs
@@ -11,6 +11,7 @@ use ruma::{
         room::{
             history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
             member::{MembershipState, RoomMemberEventContent},
+            name::RoomNameEventContent,
         },
         StateEventType,
     },
@@ -269,4 +270,16 @@ impl Service {
     ) -> Result<Option<Arc<PduEvent>>> {
         self.db.room_state_get(room_id, event_type, state_key)
     }
+
+    pub fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> {
+        services()
+            .rooms
+            .state_accessor
+            .room_state_get(&room_id, &StateEventType::RoomName, "")?
+            .map_or(Ok(None), |s| {
+                serde_json::from_str(s.content.get())
+                    .map(|c: RoomNameEventContent| c.name)
+                    .map_err(|_| Error::bad_database("Invalid room name event in database."))
+            })
+    }
 }

From 4b7d3e24dd0f8d1d9db2f7ba3ea2103a880a215c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 10 Jul 2023 16:24:57 +0200
Subject: [PATCH 2/6] bump ruma

---
 Cargo.lock | 63 ++++++++++++++++++++++++++++++++++--------------------
 Cargo.toml |  4 ++--
 2 files changed, 42 insertions(+), 25 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 487780dd..3480c014 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -422,6 +422,12 @@ version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
 
+[[package]]
+name = "const_panic"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b"
+
 [[package]]
 name = "constant_time_eq"
 version = "0.1.5"
@@ -1167,7 +1173,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
 dependencies = [
  "autocfg",
  "hashbrown 0.12.3",
- "serde",
 ]
 
 [[package]]
@@ -1178,6 +1183,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.0",
+ "serde",
 ]
 
 [[package]]
@@ -1212,9 +1218,9 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
 
 [[package]]
 name = "itertools"
-version = "0.10.5"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
 dependencies = [
  "either",
 ]
@@ -1283,25 +1289,30 @@ dependencies = [
 
 [[package]]
 name = "konst"
-version = "0.2.19"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4"
+checksum = "1d9a8bb6c7c71d151b25936b03e012a4c00daea99e3a3797c6ead66b0a0d55e2"
 dependencies = [
- "konst_macro_rules",
+ "const_panic",
+ "konst_kernel",
  "konst_proc_macros",
+ "typewit",
 ]
 
 [[package]]
-name = "konst_macro_rules"
-version = "0.2.19"
+name = "konst_kernel"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
+checksum = "55d2ab266022e7309df89ed712bddc753e3a3c395c3ced1bb2e4470ec2a8146d"
+dependencies = [
+ "typewit",
+]
 
 [[package]]
 name = "konst_proc_macros"
-version = "0.2.11"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "984e109462d46ad18314f10e392c286c3d47bce203088a09012de1015b45b737"
+checksum = "4e28ab1dc35e09d60c2b8c90d12a9a8d9666c876c10a3739a3196db0103b6043"
 
 [[package]]
 name = "lazy_static"
@@ -2111,7 +2122,7 @@ dependencies = [
 [[package]]
 name = "ruma"
 version = "0.8.2"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "assign",
  "js_int",
@@ -2129,7 +2140,7 @@ dependencies = [
 [[package]]
 name = "ruma-appservice-api"
 version = "0.8.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2140,7 +2151,7 @@ dependencies = [
 [[package]]
 name = "ruma-client-api"
 version = "0.16.2"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "assign",
  "bytes",
@@ -2157,13 +2168,13 @@ dependencies = [
 [[package]]
 name = "ruma-common"
 version = "0.11.3"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "base64 0.21.2",
  "bytes",
  "form_urlencoded",
  "http",
- "indexmap 1.9.3",
+ "indexmap 2.0.0",
  "js_int",
  "js_option",
  "konst",
@@ -2185,7 +2196,7 @@ dependencies = [
 [[package]]
 name = "ruma-federation-api"
 version = "0.7.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2196,7 +2207,7 @@ dependencies = [
 [[package]]
 name = "ruma-identifiers-validation"
 version = "0.9.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "js_int",
  "thiserror",
@@ -2205,7 +2216,7 @@ dependencies = [
 [[package]]
 name = "ruma-identity-service-api"
 version = "0.7.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2215,7 +2226,7 @@ dependencies = [
 [[package]]
 name = "ruma-macros"
 version = "0.11.3"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "once_cell",
  "proc-macro-crate",
@@ -2230,7 +2241,7 @@ dependencies = [
 [[package]]
 name = "ruma-push-gateway-api"
 version = "0.7.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "js_int",
  "ruma-common",
@@ -2241,7 +2252,7 @@ dependencies = [
 [[package]]
 name = "ruma-signatures"
 version = "0.13.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "base64 0.21.2",
  "ed25519-dalek",
@@ -2257,7 +2268,7 @@ dependencies = [
 [[package]]
 name = "ruma-state-res"
 version = "0.9.1"
-source = "git+https://github.com/timokoesters/ruma?rev=4ec9c69bb7e09391add2382b3ebac97b6e8f4c64#4ec9c69bb7e09391add2382b3ebac97b6e8f4c64"
+source = "git+https://github.com/ruma/ruma?rev=07bc06038fded40d4e9180637f056d256f9a1fbc#07bc06038fded40d4e9180637f056d256f9a1fbc"
 dependencies = [
  "itertools",
  "js_int",
@@ -3152,6 +3163,12 @@ version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
 
+[[package]]
+name = "typewit"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4061a10d4d8f3081a8ccc025182afd8434302d8d4b4503ec6d8510d09df08c2d"
+
 [[package]]
 name = "uncased"
 version = "0.9.9"
diff --git a/Cargo.toml b/Cargo.toml
index a01f410b..ae7de599 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,8 +26,8 @@ tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitiv
 
 # Used for matrix spec type definitions and helpers
 #ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
-#ruma = { git = "https://github.com/ruma/ruma", rev = "38294bd5206498c02b1001227d65654eb548308b", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
-ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
+ruma = { git = "https://github.com/ruma/ruma", rev = "07bc06038fded40d4e9180637f056d256f9a1fbc", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
+#ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
 #ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
 
 # Async runtime and utilities

From 78e7b711df213559150b5c6e7e7da1967d353e23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 10 Jul 2023 16:25:33 +0200
Subject: [PATCH 3/6] fix: better sliding sync

---
 src/api/client_server/sync.rs | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/api/client_server/sync.rs b/src/api/client_server/sync.rs
index bc89a4c3..fed4fb73 100644
--- a/src/api/client_server/sync.rs
+++ b/src/api/client_server/sync.rs
@@ -23,7 +23,7 @@ use ruma::{
     uint, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId,
 };
 use std::{
-    collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
+    collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
     sync::Arc,
     time::Duration,
 };
@@ -1246,6 +1246,18 @@ pub async fn sync_events_v4_route(
         let (timeline_pdus, limited) =
             load_timeline(&sender_user, &room_id, sincecount, timeline_limit)?;
 
+        let prev_batch = timeline_pdus
+            .first()
+            .map_or(Ok::<_, Error>(None), |(pdu_count, _)| {
+                Ok(Some(match pdu_count {
+                    PduCount::Backfilled(_) => {
+                        error!("timeline in backfill state?!");
+                        "0".to_owned()
+                    }
+                    PduCount::Normal(c) => c.to_string(),
+                }))
+            })?;
+
         let room_events: Vec<_> = timeline_pdus
             .iter()
             .map(|(_, pdu)| pdu.to_sync_room_event())
@@ -1277,7 +1289,7 @@ pub async fn sync_events_v4_route(
                 },
                 timeline: room_events,
                 required_state,
-                prev_batch: None,
+                prev_batch,
                 limited,
                 joined_count: Some(
                     (services()

From c17187777f5f3bb06183ce423d990bc1c1061929 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 10 Jul 2023 16:26:36 +0200
Subject: [PATCH 4/6] fix: never try federation with self

---
 src/api/server_server.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/api/server_server.rs b/src/api/server_server.rs
index adb5f1fb..0177f2ab 100644
--- a/src/api/server_server.rs
+++ b/src/api/server_server.rs
@@ -123,6 +123,12 @@ where
         return Err(Error::bad_config("Federation is disabled."));
     }
 
+    if destination == services().globals.server_name() {
+        return Err(Error::bad_config(
+            "Won't send federation request to ourselves",
+        ));
+    }
+
     debug!("Preparing to send request to {destination}");
 
     let mut write_destination_to_cache = false;

From edd4a3733fb6cf842155441eef86435efdd1cc21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 10 Jul 2023 16:27:42 +0200
Subject: [PATCH 5/6] fix: actually clear memory in the admin commands

---
 src/database/key_value/globals.rs | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/src/database/key_value/globals.rs b/src/database/key_value/globals.rs
index ab3dfe0e..1e024591 100644
--- a/src/database/key_value/globals.rs
+++ b/src/database/key_value/globals.rs
@@ -1,7 +1,8 @@
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashMap};
 
 use async_trait::async_trait;
 use futures_util::{stream::FuturesUnordered, StreamExt};
+use lru_cache::LruCache;
 use ruma::{
     api::federation::discovery::{ServerSigningKeys, VerifyKey},
     signatures::Ed25519KeyPair,
@@ -148,28 +149,36 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
 
     fn clear_caches(&self, amount: u32) {
         if amount > 0 {
-            self.pdu_cache.lock().unwrap().clear();
+            let c = &mut *self.pdu_cache.lock().unwrap();
+            *c = LruCache::new(c.capacity());
         }
         if amount > 1 {
-            self.shorteventid_cache.lock().unwrap().clear();
+            let c = &mut *self.shorteventid_cache.lock().unwrap();
+            *c = LruCache::new(c.capacity());
         }
         if amount > 2 {
-            self.auth_chain_cache.lock().unwrap().clear();
+            let c = &mut *self.auth_chain_cache.lock().unwrap();
+            *c = LruCache::new(c.capacity());
         }
         if amount > 3 {
-            self.eventidshort_cache.lock().unwrap().clear();
+            let c = &mut *self.eventidshort_cache.lock().unwrap();
+            *c = LruCache::new(c.capacity());
         }
         if amount > 4 {
-            self.statekeyshort_cache.lock().unwrap().clear();
+            let c = &mut *self.statekeyshort_cache.lock().unwrap();
+            *c = LruCache::new(c.capacity());
         }
         if amount > 5 {
-            self.our_real_users_cache.write().unwrap().clear();
+            let c = &mut *self.our_real_users_cache.write().unwrap();
+            *c = HashMap::new();
         }
         if amount > 6 {
-            self.appservice_in_room_cache.write().unwrap().clear();
+            let c = &mut *self.appservice_in_room_cache.write().unwrap();
+            *c = HashMap::new();
         }
         if amount > 7 {
-            self.lasttimelinecount_cache.lock().unwrap().clear();
+            let c = &mut *self.lasttimelinecount_cache.lock().unwrap();
+            *c = HashMap::new();
         }
     }
 

From 0b4e3de9c0135258eb68a0fa3cccdfff45de81d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20K=C3=B6sters?= <timo@koesters.xyz>
Date: Mon, 10 Jul 2023 16:28:08 +0200
Subject: [PATCH 6/6] fix: spaces with restricted rooms

---
 src/service/rooms/spaces/mod.rs | 125 +++++++++++++++++++-------------
 1 file changed, 74 insertions(+), 51 deletions(-)

diff --git a/src/service/rooms/spaces/mod.rs b/src/service/rooms/spaces/mod.rs
index 380f86cf..36fa1fcd 100644
--- a/src/service/rooms/spaces/mod.rs
+++ b/src/service/rooms/spaces/mod.rs
@@ -5,11 +5,10 @@ use ruma::{
     api::{
         client::{
             error::ErrorKind,
-            space::{get_hierarchy, SpaceHierarchyRoomsChunk, SpaceRoomJoinRule},
+            space::{get_hierarchy, SpaceHierarchyRoomsChunk},
         },
         federation,
     },
-    directory::PublicRoomJoinRule,
     events::{
         room::{
             avatar::RoomAvatarEventContent,
@@ -18,11 +17,11 @@ use ruma::{
             guest_access::{GuestAccess, RoomGuestAccessEventContent},
             history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
             join_rules::{JoinRule, RoomJoinRulesEventContent},
-            name::RoomNameEventContent,
             topic::RoomTopicEventContent,
         },
         StateEventType,
     },
+    space::SpaceRoomJoinRule,
     OwnedRoomId, RoomId, UserId,
 };
 
@@ -30,10 +29,15 @@ use tracing::{debug, error, warn};
 
 use crate::{services, Error, PduEvent, Result};
 
+pub enum CachedJoinRule {
+    Simplified(SpaceRoomJoinRule),
+    Full(JoinRule),
+}
+
 pub struct CachedSpaceChunk {
     chunk: SpaceHierarchyRoomsChunk,
     children: Vec<OwnedRoomId>,
-    join_rule: JoinRule,
+    join_rule: CachedJoinRule,
 }
 
 pub struct Service {
@@ -79,9 +83,15 @@ impl Service {
                 .as_ref()
             {
                 if let Some(cached) = cached {
-                    if let Some(_join_rule) =
-                        self.handle_join_rule(&cached.join_rule, sender_user, &current_room)?
-                    {
+                    let allowed = match &cached.join_rule {
+                        CachedJoinRule::Simplified(s) => {
+                            self.handle_simplified_join_rule(s, sender_user, &current_room)?
+                        }
+                        CachedJoinRule::Full(f) => {
+                            self.handle_join_rule(f, sender_user, &current_room)?
+                        }
+                    };
+                    if allowed {
                         if left_to_skip > 0 {
                             left_to_skip -= 1;
                         } else {
@@ -152,7 +162,7 @@ impl Service {
                         Some(CachedSpaceChunk {
                             chunk,
                             children: children_ids.clone(),
-                            join_rule,
+                            join_rule: CachedJoinRule::Full(join_rule),
                         }),
                     );
                 }
@@ -182,7 +192,6 @@ impl Service {
                     .await
                 {
                     warn!("Got response from {server} for /hierarchy\n{response:?}");
-                    let join_rule = self.translate_pjoinrule(&response.room.join_rule)?;
                     let chunk = SpaceHierarchyRoomsChunk {
                         canonical_alias: response.room.canonical_alias,
                         name: response.room.name,
@@ -192,7 +201,7 @@ impl Service {
                         world_readable: response.room.world_readable,
                         guest_can_join: response.room.guest_can_join,
                         avatar_url: response.room.avatar_url,
-                        join_rule: self.translate_sjoinrule(&response.room.join_rule)?,
+                        join_rule: response.room.join_rule.clone(),
                         room_type: response.room.room_type,
                         children_state: response.room.children_state,
                     };
@@ -202,9 +211,11 @@ impl Service {
                         .map(|c| c.room_id.clone())
                         .collect::<Vec<_>>();
 
-                    if let Some(_join_rule) =
-                        self.handle_join_rule(&join_rule, sender_user, &current_room)?
-                    {
+                    if self.handle_simplified_join_rule(
+                        &response.room.join_rule,
+                        sender_user,
+                        &current_room,
+                    )? {
                         if left_to_skip > 0 {
                             left_to_skip -= 1;
                         } else {
@@ -220,7 +231,7 @@ impl Service {
                         Some(CachedSpaceChunk {
                             chunk,
                             children,
-                            join_rule,
+                            join_rule: CachedJoinRule::Simplified(response.room.join_rule),
                         }),
                     );
 
@@ -349,15 +360,17 @@ impl Service {
                     })
                     .transpose()?
                     .unwrap_or(JoinRule::Invite);
-                self.handle_join_rule(&join_rule, sender_user, room_id)?
-                    .ok_or_else(|| {
-                        debug!("User is not allowed to see room {room_id}");
-                        // This error will be caught later
-                        Error::BadRequest(
-                            ErrorKind::Forbidden,
-                            "User is not allowed to see the room",
-                        )
-                    })?
+
+                if !self.handle_join_rule(&join_rule, sender_user, room_id)? {
+                    debug!("User is not allowed to see room {room_id}");
+                    // This error will be caught later
+                    return Err(Error::BadRequest(
+                        ErrorKind::Forbidden,
+                        "User is not allowed to see the room",
+                    ));
+                }
+
+                self.translate_joinrule(&join_rule)?
             },
             room_type: services()
                 .rooms
@@ -378,20 +391,35 @@ impl Service {
         })
     }
 
-    fn translate_pjoinrule(&self, join_rule: &PublicRoomJoinRule) -> Result<JoinRule> {
+    fn translate_joinrule(&self, join_rule: &JoinRule) -> Result<SpaceRoomJoinRule> {
         match join_rule {
-            PublicRoomJoinRule::Knock => Ok(JoinRule::Knock),
-            PublicRoomJoinRule::Public => Ok(JoinRule::Public),
+            JoinRule::Invite => Ok(SpaceRoomJoinRule::Invite),
+            JoinRule::Knock => Ok(SpaceRoomJoinRule::Knock),
+            JoinRule::Private => Ok(SpaceRoomJoinRule::Private),
+            JoinRule::Restricted(_) => Ok(SpaceRoomJoinRule::Restricted),
+            JoinRule::KnockRestricted(_) => Ok(SpaceRoomJoinRule::KnockRestricted),
+            JoinRule::Public => Ok(SpaceRoomJoinRule::Public),
             _ => Err(Error::BadServerResponse("Unknown join rule")),
         }
     }
 
-    fn translate_sjoinrule(&self, join_rule: &PublicRoomJoinRule) -> Result<SpaceRoomJoinRule> {
-        match join_rule {
-            PublicRoomJoinRule::Knock => Ok(SpaceRoomJoinRule::Knock),
-            PublicRoomJoinRule::Public => Ok(SpaceRoomJoinRule::Public),
-            _ => Err(Error::BadServerResponse("Unknown join rule")),
-        }
+    fn handle_simplified_join_rule(
+        &self,
+        join_rule: &SpaceRoomJoinRule,
+        sender_user: &UserId,
+        room_id: &RoomId,
+    ) -> Result<bool> {
+        let allowed = match join_rule {
+            SpaceRoomJoinRule::Public => true,
+            SpaceRoomJoinRule::Knock => true,
+            SpaceRoomJoinRule::Invite => services()
+                .rooms
+                .state_cache
+                .is_joined(sender_user, &room_id)?,
+            _ => false,
+        };
+
+        Ok(allowed)
     }
 
     fn handle_join_rule(
@@ -399,30 +427,25 @@ impl Service {
         join_rule: &JoinRule,
         sender_user: &UserId,
         room_id: &RoomId,
-    ) -> Result<Option<SpaceRoomJoinRule>> {
+    ) -> Result<bool> {
+        if self.handle_simplified_join_rule(
+            &self.translate_joinrule(join_rule)?,
+            sender_user,
+            room_id,
+        )? {
+            return Ok(true);
+        }
+
         match join_rule {
-            JoinRule::Public => Ok::<_, Error>(Some(SpaceRoomJoinRule::Public)),
-            JoinRule::Knock => Ok(Some(SpaceRoomJoinRule::Knock)),
-            JoinRule::Invite => {
-                if services()
-                    .rooms
-                    .state_cache
-                    .is_joined(sender_user, &room_id)?
-                {
-                    Ok(Some(SpaceRoomJoinRule::Invite))
-                } else {
-                    Ok(None)
-                }
-            }
-            JoinRule::Restricted(_r) => {
+            JoinRule::Restricted(_) => {
                 // TODO: Check rules
-                Ok(None)
+                Ok(false)
             }
-            JoinRule::KnockRestricted(_r) => {
+            JoinRule::KnockRestricted(_) => {
                 // TODO: Check rules
-                Ok(None)
+                Ok(false)
             }
-            _ => Ok(None),
+            _ => Ok(false),
         }
     }
 }