diff --git a/src/database.rs b/src/database.rs index 6bb1b17..06a708d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -123,6 +123,7 @@ impl Database { userid_avatarurl: db.open_tree("userid_avatarurl")?, userdeviceid_token: db.open_tree("userdeviceid_token")?, userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?, + userid_devicelistversion: db.open_tree("userid_devicelistversion")?, token_userdeviceid: db.open_tree("token_userdeviceid")?, onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?, userid_lastonetimekeyupdate: db.open_tree("userid_lastonetimekeyupdate")?, diff --git a/src/database/users.rs b/src/database/users.rs index c794e52..9cdfb5f 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -22,6 +22,7 @@ pub struct Users { pub(super) userid_avatarurl: sled::Tree, pub(super) userdeviceid_token: sled::Tree, pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists + pub(super) userid_devicelistversion: sled::Tree, // DevicelistVersion = u64 pub(super) token_userdeviceid: sled::Tree, pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + DeviceKeyId @@ -189,6 +190,10 @@ impl Users { userdeviceid.push(0xff); userdeviceid.extend_from_slice(device_id.as_bytes()); + self.userid_devicelistversion + .update_and_fetch(&user_id.as_bytes(), utils::increment)? + .expect("utils::increment will always put in a value"); + self.userdeviceid_metadata.insert( userdeviceid, serde_json::to_string(&Device { @@ -227,6 +232,10 @@ impl Users { // TODO: Remove onetimekeys + self.userid_devicelistversion + .update_and_fetch(&user_id.as_bytes(), utils::increment)? + .expect("utils::increment will always put in a value"); + self.userdeviceid_metadata.remove(&userdeviceid)?; Ok(()) @@ -811,6 +820,10 @@ impl Users { // Only existing devices should be able to call this. assert!(self.userdeviceid_metadata.get(&userdeviceid)?.is_some()); + self.userid_devicelistversion + .update_and_fetch(&user_id.as_bytes(), utils::increment)? + .expect("utils::increment will always put in a value"); + self.userdeviceid_metadata.insert( userdeviceid, serde_json::to_string(device) @@ -840,6 +853,16 @@ impl Users { }) } + pub fn get_devicelist_version(&self, user_id: &UserId) -> Result> { + self.userid_devicelistversion + .get(user_id.as_bytes())? + .map_or(Ok(None), |bytes| { + utils::u64_from_bytes(&bytes) + .map_err(|_| Error::bad_database("Invalid devicelistversion in db.")) + .map(Some) + }) + } + pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator> { let mut key = user_id.as_bytes().to_vec(); key.push(0xff); diff --git a/src/main.rs b/src/main.rs index 3157023..ba8448d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,6 +171,7 @@ fn setup_rocket() -> (rocket::Rocket, Config) { server_server::create_join_event_template_route, server_server::create_join_event_route, server_server::create_invite_route, + server_server::get_devices_route, server_server::get_room_information_route, server_server::get_profile_information_route, ], diff --git a/src/server_server.rs b/src/server_server.rs index 5779b90..e969b31 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -8,6 +8,7 @@ use ruma::{ api::{ client::error::ErrorKind, federation::{ + device::get_devices::{self, v1::UserDevice}, directory::{get_public_rooms, get_public_rooms_filtered}, discovery::{ get_remote_server_keys, get_server_keys, get_server_version, ServerSigningKeys, @@ -1979,6 +1980,46 @@ pub async fn create_invite_route<'a>( .into()) } +#[cfg_attr( + feature = "conduit_bin", + get("/_matrix/federation/v1/user/devices/<_>", data = "") +)] +#[tracing::instrument(skip(db, body))] +pub fn get_devices_route<'a>( + db: State<'a, Database>, + body: Ruma>, +) -> ConduitResult { + if !db.globals.allow_federation() { + return Err(Error::bad_config("Federation is disabled.")); + } + + Ok(get_devices::v1::Response { + user_id: body.user_id.clone(), + stream_id: db + .users + .get_devicelist_version(&body.user_id)? + .unwrap_or(0) + .try_into() + .expect("version will not grow that large"), + devices: db + .users + .all_devices_metadata(&body.user_id) + .filter_map(|r| r.ok()) + .filter_map(|metadata| { + Some(UserDevice { + keys: db + .users + .get_device_keys(&body.user_id, &metadata.device_id) + .ok()??, + device_id: metadata.device_id, + device_display_name: metadata.display_name, + }) + }) + .collect(), + } + .into()) +} + #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/query/directory", data = "")