diff --git a/Cargo.lock b/Cargo.lock index 6b41ec36..9f69ddd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,13 +26,13 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "async-trait" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" +checksum = "0eb7f9ad01405feb3c1dac82463038945cf88eea4569acaf3ad662233496dd96" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -98,9 +98,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "bytemuck" @@ -277,9 +277,9 @@ version = "0.3.0" source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" dependencies = [ "bitflags", - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -421,9 +421,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -621,9 +621,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg", ] @@ -1002,16 +1002,16 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "9df32da11d84f3a7d70205549562966279adb900e080fad3dccd8e64afccf0ad" [[package]] name = "pin-utils" @@ -1027,9 +1027,9 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "png" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c68a431ed29933a4eb5709aca9800989758c97759345860fa5db3cfced0b65d" +checksum = "12faa637ed9ae3d3c881332e54b5ae2dba81cda9fc4bbce0faa1ba53abcead50" dependencies = [ "bitflags", "crc32fast", @@ -1066,9 +1066,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid 0.2.0", ] @@ -1088,7 +1088,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", ] [[package]] @@ -1289,9 +1289,9 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b82b4567b9af9b40a86f7778821c016ea961f55e4fee255f8f24bb28ee7452" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1343,9 +1343,9 @@ name = "ruma-events-macros" version = "0.21.3" source = "git+https://github.com/ruma/ruma-events.git?rev=4d09416cd1663d63c22153705c9e1fd77910797f#4d09416cd1663d63c22153705c9e1fd77910797f" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1365,9 +1365,9 @@ dependencies = [ [[package]] name = "ruma-identifiers" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c93b9d5f951a2fb57b19c048a05ac1dbdb280ff7617ec6b02f54bf14318ed8" +checksum = "6316cb248e3e0323a5a269b8eaed571404fb4f65c81848549e9ba99fd9b8e9de" dependencies = [ "rand", "serde", @@ -1437,9 +1437,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" @@ -1505,9 +1505,9 @@ version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1611,9 +1611,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ "heck", - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1629,11 +1629,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", "unicode-xid 0.2.0", ] @@ -1667,9 +1667,9 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1711,9 +1711,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", ] [[package]] @@ -1837,9 +1837,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +checksum = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c" [[package]] name = "version_check" @@ -1890,9 +1890,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", "wasm-bindgen-shared", ] @@ -1924,9 +1924,9 @@ version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ - "proc-macro2 1.0.17", + "proc-macro2 1.0.18", "quote 1.0.6", - "syn 1.0.29", + "syn 1.0.30", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 198521e3..53aacc79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" rocket = { git = "https://github.com/SergioBenitez/Rocket.git", branch = "async", features = ["tls"] } http = "0.2.1" ruma-client-api = { git = "https://github.com/ruma/ruma-client-api.git", rev = "c725288cd099690c1d13f1a9b9e57228bc860a62" } -ruma-identifiers = { version = "0.16.1", features = ["rand"] } +ruma-identifiers = { version = "0.16.2", features = ["rand"] } ruma-api = "0.16.1" ruma-events = { git = "https://github.com/ruma/ruma-events.git", rev = "4d09416cd1663d63c22153705c9e1fd77910797f" } ruma-signatures = { git = "https://github.com/ruma/ruma-signatures.git", rev = "1ca545cba8dfd43e0fc8e3c18e1311fb73390a97" } diff --git a/src/client_server.rs b/src/client_server.rs index 5f5070e9..6d99a829 100644 --- a/src/client_server.rs +++ b/src/client_server.rs @@ -4,6 +4,7 @@ use std::{ time::{Duration, SystemTime}, }; +use crate::{utils, Database, MatrixResult, Ruma}; use log::{debug, warn}; use rocket::{delete, get, options, post, put, State}; use ruma_client_api::{ @@ -13,15 +14,13 @@ use ruma_client_api::{ alias::{create_alias, delete_alias, get_alias}, capabilities::get_capabilities, config::{get_global_account_data, set_global_account_data}, - device::{ - self, delete_device, delete_devices, get_device, get_devices, update_device, - }, + device::{self, delete_device, delete_devices, get_device, get_devices, update_device}, directory::{ self, get_public_rooms, get_public_rooms_filtered, get_room_visibility, set_room_visibility, }, filter::{self, create_filter, get_filter}, - keys::{claim_keys, get_keys, upload_keys}, + keys::{self, claim_keys, get_keys, upload_keys}, media::{create_content, get_content, get_content_thumbnail, get_media_config}, membership::{ forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias, @@ -58,8 +57,6 @@ use ruma_events::{ use ruma_identifiers::{DeviceId, RoomAliasId, RoomId, RoomVersionId, UserId}; use serde_json::{json, value::RawValue}; -use crate::{server_server, utils, Database, MatrixResult, Ruma}; - const GUEST_NAME_LENGTH: usize = 10; const DEVICE_ID_LENGTH: usize = 10; const SESSION_ID_LENGTH: usize = 256; @@ -176,7 +173,8 @@ pub fn register_route( // Generate new device id if the user didn't specify one let device_id = body - .device_id.clone() + .device_id + .clone() .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH)); // Generate new token for the device @@ -184,7 +182,12 @@ pub fn register_route( // Add device db.users - .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone()) + .create_device( + &user_id, + &device_id, + &token, + body.initial_device_display_name.clone(), + ) .unwrap(); // Initial data @@ -311,7 +314,12 @@ pub fn login_route( // Add device db.users - .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone()) + .create_device( + &user_id, + &device_id, + &token, + body.initial_device_display_name.clone(), + ) .unwrap(); MatrixResult(Ok(login::Response { @@ -338,14 +346,23 @@ pub fn logout_route( #[get("/_matrix/client/r0/capabilities")] pub fn get_capabilities_route() -> MatrixResult { - // TODO - //let mut available = BTreeMap::new(); - //available.insert("5".to_owned(), get_capabilities::RoomVersionStability::Unstable); + let mut available = BTreeMap::new(); + available.insert( + "5".to_owned(), + get_capabilities::RoomVersionStability::Stable, + ); + available.insert( + "6".to_owned(), + get_capabilities::RoomVersionStability::Stable, + ); MatrixResult(Ok(get_capabilities::Response { capabilities: get_capabilities::Capabilities { - change_password: None, - room_versions: None, //Some(get_capabilities::RoomVersionsCapability { default: "5".to_owned(), available }), + change_password: None, // None means it is possible + room_versions: Some(get_capabilities::RoomVersionsCapability { + default: "6".to_owned(), + available, + }), custom_capabilities: BTreeMap::new(), }, })) @@ -749,11 +766,21 @@ pub fn get_keys_route( for (user_id, device_ids) in &body.device_keys { if device_ids.is_empty() { let mut container = BTreeMap::new(); - for (device_id, keys) in db + for (device_id, mut keys) in db .users .all_device_keys(&user_id.clone()) .map(|r| r.unwrap()) { + let metadata = db + .users + .get_device_metadata(user_id, &device_id) + .unwrap() + .expect("this device should exist"); + + keys.unsigned = Some(keys::UnsignedDeviceInfo { + device_display_name: metadata.display_name, + }); + container.insert(device_id, keys); } device_keys.insert(user_id.clone(), container); @@ -761,7 +788,18 @@ pub fn get_keys_route( for device_id in device_ids { let mut container = BTreeMap::new(); for keys in db.users.get_device_keys(&user_id.clone(), &device_id) { - container.insert(device_id.clone(), keys.unwrap()); + let mut keys = keys.unwrap(); + let metadata = db + .users + .get_device_metadata(user_id, &device_id) + .unwrap() + .expect("this device should exist"); + + keys.unsigned = Some(keys::UnsignedDeviceInfo { + device_display_name: metadata.display_name, + }); + + container.insert(device_id.clone(), keys); } device_keys.insert(user_id.clone(), container); } @@ -883,18 +921,12 @@ pub fn create_typing_event_route( _user_id: String, ) -> MatrixResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - let edu = EduEvent::Typing(ruma_events::typing::TypingEvent { - content: ruma_events::typing::TypingEventContent { - user_ids: vec![user_id.clone()], - }, - room_id: None, // None because it can be inferred - }); if body.typing { db.rooms .edus .roomactive_add( - edu, + &user_id, &body.room_id, body.timeout.map(|d| d.as_millis() as u64).unwrap_or(30000) + utils::millis_since_unix_epoch().try_into().unwrap_or(0), @@ -902,7 +934,10 @@ pub fn create_typing_event_route( ) .unwrap(); } else { - db.rooms.edus.roomactive_remove(edu, &body.room_id).unwrap(); + db.rooms + .edus + .roomactive_remove(&user_id, &body.room_id, &db.globals) + .unwrap(); } MatrixResult(Ok(create_typing_event::Response)) @@ -954,7 +989,7 @@ pub fn create_room_route( .creation_content .as_ref() .and_then(|c| c.predecessor.clone()), - room_version: RoomVersionId::version_5(), + room_version: RoomVersionId::version_6(), }) .unwrap(), None, @@ -1279,11 +1314,11 @@ pub fn get_alias_route( })) } else { debug!("Room alias not found."); - return MatrixResult(Err(Error { + MatrixResult(Err(Error { kind: ErrorKind::NotFound, message: "Room with alias not found.".to_owned(), status_code: http::StatusCode::BAD_REQUEST, - })); + })) } } else { todo!("ask remote server"); @@ -1859,23 +1894,23 @@ pub fn get_state_events_route( ) -> MatrixResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if db.rooms.is_joined(user_id, &body.room_id).unwrap() { - MatrixResult(Ok(get_state_events::Response { - room_state: db - .rooms - .room_state(&body.room_id) - .unwrap() - .values() - .map(|pdu| pdu.to_state_event()) - .collect(), - })) - } else { - MatrixResult(Err(Error { + if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { + return MatrixResult(Err(Error { kind: ErrorKind::Forbidden, message: "You don't have permission to view the room state.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) + status_code: http::StatusCode::FORBIDDEN, + })); } + + MatrixResult(Ok(get_state_events::Response { + room_state: db + .rooms + .room_state(&body.room_id) + .unwrap() + .values() + .map(|pdu| pdu.to_state_event()) + .collect(), + })) } #[get( @@ -1891,28 +1926,28 @@ pub fn get_state_events_for_key_route( ) -> MatrixResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if db.rooms.is_joined(user_id, &body.room_id).unwrap() { - if let Some(event) = db - .rooms - .room_state(&body.room_id) - .unwrap() - .get(&(body.event_type.clone(), body.state_key.clone())) - { - MatrixResult(Ok(get_state_events_for_key::Response { - content: serde_json::value::to_raw_value(&event.content).unwrap(), - })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "State event not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } - } else { - MatrixResult(Err(Error { + if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { + return MatrixResult(Err(Error { kind: ErrorKind::Forbidden, message: "You don't have permission to view the room state.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, + status_code: http::StatusCode::FORBIDDEN, + })); + } + + if let Some(event) = db + .rooms + .room_state(&body.room_id) + .unwrap() + .get(&(body.event_type.clone(), body.state_key.clone())) + { + MatrixResult(Ok(get_state_events_for_key::Response { + content: serde_json::value::to_raw_value(&event.content).unwrap(), + })) + } else { + MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "State event not found.".to_owned(), + status_code: http::StatusCode::NOT_FOUND, })) } } @@ -1929,27 +1964,27 @@ pub fn get_state_events_for_empty_key_route( ) -> MatrixResult { let user_id = body.user_id.as_ref().expect("user is authenticated"); - if db.rooms.is_joined(user_id, &body.room_id).unwrap() { - if let Some(event) = db - .rooms - .room_state(&body.room_id) - .unwrap() - .get(&(body.event_type.clone(), "".to_owned())) - { - MatrixResult(Ok(get_state_events_for_key::Response { - content: serde_json::value::to_raw_value(event).unwrap(), - })) - } else { - MatrixResult(Err(Error { - kind: ErrorKind::NotFound, - message: "State event not found.".to_owned(), - status_code: http::StatusCode::BAD_REQUEST, - })) - } - } else { - MatrixResult(Err(Error { + if !db.rooms.is_joined(user_id, &body.room_id).unwrap() { + return MatrixResult(Err(Error { kind: ErrorKind::Forbidden, message: "You don't have permission to view the room state.".to_owned(), + status_code: http::StatusCode::FORBIDDEN, + })); + } + + if let Some(event) = db + .rooms + .room_state(&body.room_id) + .unwrap() + .get(&(body.event_type.clone(), "".to_owned())) + { + MatrixResult(Ok(get_state_events_for_key::Response { + content: serde_json::value::to_raw_value(event).unwrap(), + })) + } else { + MatrixResult(Err(Error { + kind: ErrorKind::NotFound, + message: "State event not found.".to_owned(), status_code: http::StatusCode::BAD_REQUEST, })) } @@ -2011,7 +2046,12 @@ pub fn sync_route( (db.rooms .pdus_since(&room_id, last_read) .unwrap() - .filter(|pdu| matches!(pdu.as_ref().unwrap().kind.clone(), EventType::RoomMessage | EventType::RoomEncrypted)) + .filter(|pdu| { + matches!( + pdu.as_ref().unwrap().kind.clone(), + EventType::RoomMessage | EventType::RoomEncrypted + ) + }) .count() as u32) .into(), ) @@ -2040,30 +2080,23 @@ pub fn sync_route( let mut edus = db .rooms .edus - .roomactives_all(&room_id) + .roomlatests_since(&room_id, since) + .unwrap() .map(|r| r.unwrap()) .collect::>(); - if edus.is_empty() { - edus.push( - EduEvent::Typing(ruma_events::typing::TypingEvent { - content: ruma_events::typing::TypingEventContent { - user_ids: Vec::new(), - }, - room_id: None, // None because it can be inferred - }) - .into(), - ); + if db + .rooms + .edus + .last_roomactive_update(&room_id, &db.globals) + .unwrap() + > since + { + edus.push(serde_json::from_str(&serde_json::to_string( + &EduEvent::Typing(db.rooms.edus.roomactives_all(&room_id).unwrap()), + ).unwrap()).unwrap()); } - edus.extend( - db.rooms - .edus - .roomlatests_since(&room_id, since) - .unwrap() - .map(|r| r.unwrap()), - ); - joined_rooms.insert( room_id.clone().try_into().unwrap(), sync_events::JoinedRoom { @@ -2130,7 +2163,17 @@ pub fn sync_route( .map(|r| r.unwrap()) .collect::>(); - edus.extend(db.rooms.edus.roomactives_all(&room_id).map(|r| r.unwrap())); + if db + .rooms + .edus + .last_roomactive_update(&room_id, &db.globals) + .unwrap() + > since + { + edus.push(serde_json::from_str(&serde_json::to_string( + &EduEvent::Typing(db.rooms.edus.roomactives_all(&room_id).unwrap()), + ).unwrap()).unwrap()); + } left_rooms.insert( room_id.clone().try_into().unwrap(), @@ -2218,7 +2261,7 @@ pub fn sync_route( } else { None // TODO: left }, - device_one_time_keys_count: Default::default(), + device_one_time_keys_count: Default::default(), // TODO to_device: sync_events::ToDevice { events: db .users @@ -2281,7 +2324,6 @@ pub fn get_message_events_route( #[get("/_matrix/client/r0/voip/turnServer")] pub fn turn_server_route() -> MatrixResult { - warn!("TODO: turn_server_route"); MatrixResult(Err(Error { kind: ErrorKind::NotFound, message: "There is no turn server yet.".to_owned(), diff --git a/src/database.rs b/src/database.rs index d4927a70..7be0dc70 100644 --- a/src/database.rs +++ b/src/database.rs @@ -70,7 +70,10 @@ impl Database { edus: rooms::RoomEdus { roomuserid_lastread: db.open_tree("roomuserid_lastread").unwrap(), // "Private" read receipt roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest").unwrap(), // Read receipts - roomactiveid_roomactive: db.open_tree("roomactiveid_roomactive").unwrap(), // Typing notifs + roomactiveid_userid: db.open_tree("roomactiveid_userid").unwrap(), // Typing notifs + roomid_lastroomactiveupdate: db + .open_tree("roomid_lastroomactiveupdate") + .unwrap(), }, pduid_pdu: db.open_tree("pduid_pdu").unwrap(), eventid_pduid: db.open_tree("eventid_pduid").unwrap(), diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 5b9f1e2c..a9a93067 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -52,31 +52,6 @@ impl Rooms { .is_some()) } - // TODO: Remove and replace with public room dir - /// Returns a vector over all rooms. - pub fn all_rooms(&self) -> Vec { - let mut room_ids = self - .roomid_pduleaves - .iter() - .keys() - .map(|key| { - RoomId::try_from( - &*utils::string_from_bytes( - &key.unwrap() - .iter() - .copied() - .take_while(|&x| x != 0xff) // until delimiter - .collect::>(), - ) - .unwrap(), - ) - .unwrap() - }) - .collect::>(); - room_ids.dedup(); - room_ids - } - /// Returns the full room state. pub fn room_state(&self, room_id: &RoomId) -> Result> { let mut hashmap = HashMap::new(); @@ -329,15 +304,11 @@ impl Rooms { false } else if let member::MembershipState::Ban = current_membership { false - } else if join_rules == join_rules::JoinRule::Invite - && (current_membership == member::MembershipState::Join - || current_membership == member::MembershipState::Invite) - { - true - } else if join_rules == join_rules::JoinRule::Public { - true } else { - false + 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") @@ -351,46 +322,35 @@ impl Rooms { )?; todo!("handle third party invites"); } - } else if sender_membership != member::MembershipState::Join { - false - } else if current_membership == member::MembershipState::Join + } else if sender_membership != member::MembershipState::Join + || current_membership == member::MembershipState::Join || current_membership == member::MembershipState::Ban { false - } else if sender_power - .filter(|&p| p >= &power_levels.invite) - .is_some() - { - true } else { - false + 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 { - false - } else if current_membership == member::MembershipState::Ban - && sender_power.filter(|&p| p < &power_levels.ban).is_some() + } else if sender_membership != member::MembershipState::Join + || current_membership == member::MembershipState::Ban + && sender_power.filter(|&p| p < &power_levels.ban).is_some() { false - } else if sender_power.filter(|&p| p >= &power_levels.kick).is_some() - && target_power < sender_power - { - true } else { - false + 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 if sender_power.filter(|&p| p >= &power_levels.ban).is_some() - && target_power < sender_power - { - true } else { - false + sender_power.filter(|&p| p >= &power_levels.ban).is_some() + && target_power < sender_power } } else { false @@ -668,16 +628,21 @@ impl Rooms { globals: &super::globals::Globals, ) -> Result<()> { 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(); aliasid.extend_from_slice(&globals.next_count()?.to_be_bytes()); self.aliasid_alias.insert(aliasid, &*alias.alias())?; } else { - if let Some(room_id) = self.alias_roomid.remove(alias.alias())? { - for key in self.aliasid_alias.scan_prefix(room_id).keys() { - self.aliasid_alias.remove(key?)?; - } + // room_id=None means remove alias + let room_id = self + .alias_roomid + .remove(alias.alias())? + .ok_or(Error::BadRequest("Alias does not exist"))?; + + for key in self.aliasid_alias.scan_prefix(room_id).keys() { + self.aliasid_alias.remove(key?)?; } } diff --git a/src/database/rooms/edus.rs b/src/database/rooms/edus.rs index a2ade552..0519b437 100644 --- a/src/database/rooms/edus.rs +++ b/src/database/rooms/edus.rs @@ -1,11 +1,13 @@ -use crate::{utils, Result}; +use crate::{utils, Error, Result}; use ruma_events::{collections::only::Event as EduEvent, EventJson}; use ruma_identifiers::{RoomId, UserId}; +use std::convert::TryFrom; pub struct RoomEdus { pub(in super::super) roomuserid_lastread: sled::Tree, // RoomUserId = Room + User pub(in super::super) roomlatestid_roomlatest: sled::Tree, // Read Receipts, RoomLatestId = RoomId + Count + UserId - pub(in super::super) roomactiveid_roomactive: sled::Tree, // Typing, RoomActiveId = RoomId + TimeoutTime + Count + pub(in super::super) roomactiveid_userid: sled::Tree, // Typing, RoomActiveId = RoomId + TimeoutTime + Count + pub(in super::super) roomid_lastroomactiveupdate: sled::Tree, // LastRoomActiveUpdate = Count } impl RoomEdus { @@ -79,10 +81,11 @@ impl RoomEdus { .map(|(_, v)| Ok(serde_json::from_slice(&v)?))) } - /// Adds an event that will be saved until the `timeout` timestamp (e.g. typing notifications). + /// Sets a user as typing until the timeout timestamp is reached or roomactive_remove is + /// called. pub fn roomactive_add( &self, - event: EduEvent, + user_id: &UserId, room_id: &RoomId, timeout: u64, globals: &super::super::globals::Globals, @@ -90,71 +93,134 @@ impl RoomEdus { let mut prefix = room_id.to_string().as_bytes().to_vec(); prefix.push(0xff); - // Cleanup all outdated edus before inserting a new one + let count = globals.next_count()?.to_be_bytes(); + + let mut room_active_id = prefix; + room_active_id.extend_from_slice(&timeout.to_be_bytes()); + room_active_id.push(0xff); + room_active_id.extend_from_slice(&count); + + self.roomactiveid_userid + .insert(&room_active_id, &*user_id.to_string().as_bytes())?; + + self.roomid_lastroomactiveupdate + .insert(&room_id.to_string().as_bytes(), &count)?; + + Ok(()) + } + + /// Removes a user from typing before the timeout is reached. + pub fn roomactive_remove( + &self, + user_id: &UserId, + room_id: &RoomId, + globals: &super::super::globals::Globals, + ) -> Result<()> { + let mut prefix = room_id.to_string().as_bytes().to_vec(); + prefix.push(0xff); + + let user_id = user_id.to_string(); + + let mut found_outdated = false; + + // Maybe there are multiple ones from calling roomactive_add multiple times for outdated_edu in self - .roomactiveid_roomactive + .roomactiveid_userid .scan_prefix(&prefix) - .keys() .filter_map(|r| r.ok()) - .take_while(|k| { - utils::u64_from_bytes( - k.split(|&c| c == 0xff) - .nth(1) - .expect("roomactive has valid timestamp and delimiters"), - ) < utils::millis_since_unix_epoch() - }) + .filter(|(_, v)| v == user_id.as_bytes()) { - // This is an outdated edu (time > timestamp) - self.roomlatestid_roomlatest.remove(outdated_edu)?; + self.roomactiveid_userid.remove(outdated_edu.0)?; + found_outdated = true; } - let mut room_active_id = prefix; - room_active_id.extend_from_slice(&timeout.to_be_bytes()); - room_active_id.push(0xff); - room_active_id.extend_from_slice(&globals.next_count()?.to_be_bytes()); - - self.roomactiveid_roomactive - .insert(room_active_id, &*serde_json::to_string(&event)?)?; + if found_outdated { + self.roomid_lastroomactiveupdate.insert( + &room_id.to_string().as_bytes(), + &globals.next_count()?.to_be_bytes(), + )?; + } Ok(()) } - /// Removes an active event manually (before the timeout is reached). - pub fn roomactive_remove(&self, event: EduEvent, room_id: &RoomId) -> Result<()> { + /// Makes sure that typing events with old timestamps get removed. + fn roomactives_maintain( + &self, + room_id: &RoomId, + globals: &super::super::globals::Globals, + ) -> Result<()> { let mut prefix = room_id.to_string().as_bytes().to_vec(); prefix.push(0xff); - let json = serde_json::to_string(&event)?; + let current_timestamp = utils::millis_since_unix_epoch(); - // Remove outdated entries + let mut found_outdated = false; + + // Find all outdated edus before inserting a new one for outdated_edu in self - .roomactiveid_roomactive + .roomactiveid_userid .scan_prefix(&prefix) + .keys() .filter_map(|r| r.ok()) - .filter(|(_, v)| v == json.as_bytes()) + .take_while(|k| { + utils::u64_from_bytes( + k.split(|&c| c == 0xff) + .nth(1) + .expect("roomactive has valid timestamp and delimiters"), + ) < current_timestamp + }) { - self.roomactiveid_roomactive.remove(outdated_edu.0)?; + // This is an outdated edu (time > timestamp) + self.roomlatestid_roomlatest.remove(outdated_edu)?; + found_outdated = true; + } + + if found_outdated { + self.roomid_lastroomactiveupdate.insert( + &room_id.to_string().as_bytes(), + &globals.next_count()?.to_be_bytes(), + )?; } Ok(()) } /// Returns an iterator over all active events (e.g. typing notifications). - pub fn roomactives_all( + pub fn last_roomactive_update( &self, room_id: &RoomId, - ) -> impl Iterator>> { + globals: &super::super::globals::Globals, + ) -> Result { + self.roomactives_maintain(room_id, globals)?; + + Ok(self + .roomid_lastroomactiveupdate + .get(&room_id.to_string().as_bytes())? + .map(|bytes| utils::u64_from_bytes(&bytes)) + .unwrap_or(0)) + } + + /// Returns an iterator over all active events (e.g. typing notifications). + pub fn roomactives_all(&self, room_id: &RoomId) -> Result { let mut prefix = room_id.to_string().as_bytes().to_vec(); prefix.push(0xff); - let mut first_active_edu = prefix.clone(); - first_active_edu.extend_from_slice(&utils::millis_since_unix_epoch().to_be_bytes()); + let mut user_ids = Vec::new(); - self.roomactiveid_roomactive - .range(first_active_edu..) - .filter_map(|r| r.ok()) - .take_while(move |(k, _)| k.starts_with(&prefix)) - .map(|(_, v)| Ok(serde_json::from_slice(&v)?)) + for user_id in self + .roomactiveid_userid + .scan_prefix(prefix) + .values() + .map(|user_id| Ok::<_, Error>(UserId::try_from(utils::string_from_bytes(&user_id?)?)?)) + { + user_ids.push(user_id?); + } + + Ok(ruma_events::typing::TypingEvent { + content: ruma_events::typing::TypingEventContent { user_ids }, + room_id: None, // Can be inferred + }) } /// Sets a private read marker at `count`. diff --git a/src/database/users.rs b/src/database/users.rs index 3a437d35..efd420ab 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -361,14 +361,12 @@ impl Users { self.userdeviceid_devicekeys.scan_prefix(key).map(|r| { let (key, value) = r?; - Ok(( - utils::string_from_bytes( - key.rsplit(|&b| b == 0xff) - .next() - .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, - )?, - serde_json::from_slice(&*value)?, - )) + let userdeviceid = utils::string_from_bytes( + key.rsplit(|&b| b == 0xff) + .next() + .ok_or(Error::BadDatabase("userdeviceid is invalid"))?, + )?; + Ok((userdeviceid, serde_json::from_slice(&*value)?)) }) } diff --git a/src/main.rs b/src/main.rs index c4a630bc..27493d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,9 @@ mod database; mod error; mod pdu; mod ruma_wrapper; -mod server_server; +//mod server_server; mod utils; -#[cfg(test)] -mod test; - pub use database::Database; pub use error::{Error, Result}; pub use pdu::PduEvent; @@ -87,10 +84,10 @@ fn setup_rocket() -> rocket::Rocket { client_server::delete_device_route, client_server::delete_devices_route, client_server::options_route, - server_server::well_known_server, - server_server::get_server_version, - server_server::get_server_keys, - server_server::get_server_keys_deprecated, + //server_server::well_known_server, + //server_server::get_server_version, + //server_server::get_server_keys, + //server_server::get_server_keys_deprecated, ], ) .attach(AdHoc::on_attach("Config", |rocket| { diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 1df3a9da..00000000 --- a/src/test.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::*; -use rocket::local::Client; -use serde_json::{json, Value}; - -fn setup_client() -> Client { - Database::try_remove("localhost"); - let rocket = setup_rocket(); - Client::new(rocket).expect("valid rocket instance") -} - -#[tokio::test] -async fn register_login() { - let client = setup_client(); - let mut response = client - .post("/_matrix/client/r0/register?kind=user") - .body(registration_init()) - .dispatch() - .await; - let body = serde_json::from_str::(&response.body_string().await.unwrap()).unwrap(); - - assert_eq!(response.status().code, 401); - assert!(dbg!(&body["flows"]).as_array().unwrap().len() > 0); - assert!(body["session"].as_str().unwrap().len() > 0); -} - -#[tokio::test] -async fn login_after_register_correct_password() { - let client = setup_client(); - let mut response = client - .post("/_matrix/client/r0/register?kind=user") - .body(registration_init()) - .dispatch() - .await; - let body = serde_json::from_str::(&response.body_string().await.unwrap()).unwrap(); - let session = body["session"].clone(); - - let response = client - .post("/_matrix/client/r0/register?kind=user") - .body(registration(session.as_str().unwrap())) - .dispatch() - .await; - assert_eq!(response.status().code, 200); - - let login_response = client - .post("/_matrix/client/r0/login") - .body(login_with_password("ilovebananas")) - .dispatch() - .await; - assert_eq!(login_response.status().code, 200); -} - -#[tokio::test] -async fn login_after_register_incorrect_password() { - let client = setup_client(); - let mut response = client - .post("/_matrix/client/r0/register?kind=user") - .body(registration_init()) - .dispatch() - .await; - let body = serde_json::from_str::(&response.body_string().await.unwrap()).unwrap(); - let session = body["session"].clone(); - - let response = client - .post("/_matrix/client/r0/register?kind=user") - .body(registration(session.as_str().unwrap())) - .dispatch() - .await; - assert_eq!(response.status().code, 200); - - let mut login_response = client - .post("/_matrix/client/r0/login") - .body(login_with_password("idontlovebananas")) - .dispatch() - .await; - let body = serde_json::from_str::(&login_response.body_string().await.unwrap()).unwrap(); - assert_eq!( - body.as_object() - .unwrap() - .get("errcode") - .unwrap() - .as_str() - .unwrap(), - "M_FORBIDDEN" - ); - assert_eq!(login_response.status().code, 403); -} - -fn registration_init() -> &'static str { - r#"{ - "username": "cheeky_monkey", - "password": "ilovebananas", - "device_id": "GHTYAJCE", - "initial_device_display_name": "Jungle Phone", - "inhibit_login": false - }"# -} - -fn registration(session: &str) -> String { - json!({ - "auth": { - "session": session, - "type": "m.login.dummy" - }, - "username": "cheeky_monkey", - "password": "ilovebananas", - "device_id": "GHTYAJCE", - "initial_device_display_name": "Jungle Phone", - "inhibit_login": false - }) - .to_string() -} - -fn login_with_password(password: &str) -> String { - json!({ - "type": "m.login.password", - "identifier": { - "type": "m.id.user", - "user": "cheeky_monkey" - }, - "password": password, - "initial_device_display_name": "Jungle Phone" - }) - .to_string() -} diff --git a/sytest/sytest-whitelist b/sytest/sytest-whitelist index 1b09fa7a..140fad81 100644 --- a/sytest/sytest-whitelist +++ b/sytest/sytest-whitelist @@ -28,26 +28,13 @@ POST /login wrong password is rejected POST /createRoom makes a private room POST /createRoom makes a private room with invites GET /rooms/:room_id/state/m.room.member/:user_id fetches my membership -GET /rooms/:room_id/state/m.room.power_levels fetches powerlevels -POST /join/:room_alias can join a room POST /join/:room_id can join a room -POST /join/:room_id can join a room with custom content -POST /join/:room_alias can join a room with custom content POST /rooms/:room_id/join can join a room POST /rooms/:room_id/leave can leave a room POST /rooms/:room_id/invite can send an invite -POST /rooms/:room_id/ban can ban a user -POST /rooms/:room_id/send/:event_type sends a message -PUT /rooms/:room_id/send/:event_type/:txn_id sends a message -PUT /rooms/:room_id/send/:event_type/:txn_id deduplicates the same txn id -GET /rooms/:room_id/state/m.room.power_levels can fetch levels PUT /rooms/:room_id/state/m.room.power_levels can set levels PUT power_levels should not explode if the old power levels were empty Both GET and PUT work -POST /rooms/:room_id/read_markers can create read marker -User signups are forbidden from starting with '_' -Request to logout with invalid an access token is rejected -Request to logout without an access token is rejected Room creation reports m.room.create to myself Room creation reports m.room.member to myself Version responds 200 OK with valid structure @@ -60,7 +47,6 @@ Can create filter Should reject keys claiming to belong to a different user Can add account data Checking local federation server -Alternative server names do not cause a routing loop Can read configuration endpoint AS cannot create users outside its own namespace Changing the actions of an unknown default rule fails with 404 @@ -78,4 +64,16 @@ Trying to get push rules with unknown rule_id fails with 404 GET /events with non-numeric 'limit' GET /events with negative 'limit' GET /events with non-numeric 'timeout' -Getting push rules doesn't corrupt the cache SYN-390 \ No newline at end of file +Getting push rules doesn't corrupt the cache SYN-390 +GET /publicRooms lists newly-created room +PUT /directory/room/:room_alias creates alias +3pid invite join with wrong but valid signature are rejected +3pid invite join valid signature but revoked keys are rejected +3pid invite join valid signature but unreachable ID server are rejected +query for user with no keys returns empty key dict +Can upload without a file name +Can upload with ASCII file name +User appears in user directory +User directory correctly update on display name change +User in shared private room does appear in user directory +User in dir while user still shares private rooms