diff --git a/README.md b/README.md index dc91303..acd70d2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ A Matrix Homeserver that's faster than others. - [x] Join rooms, lookup room ids - [x] Basic Riot web support - [x] Riot room discovery -- [ ] Riot read receipts +- [x] Riot read receipts +- [x] Typing indications - [ ] Riot presence - [ ] Password hashing - [ ] Proper room creation diff --git a/Rocket.toml b/Rocket.toml index f55e107..99c136d 100644 --- a/Rocket.toml +++ b/Rocket.toml @@ -2,6 +2,6 @@ address = "0.0.0.0" port = 14004 -#[global.tls] -#certs = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/fullchain.pem" -#key = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/privkey.pem" +[global.tls] +certs = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/fullchain.pem" +key = "/etc/letsencrypt/live/matrixtesting.koesters.xyz/privkey.pem" diff --git a/src/client_server.rs b/src/client_server.rs index 3c36c7c..660da6c 100644 --- a/src/client_server.rs +++ b/src/client_server.rs @@ -27,6 +27,7 @@ use ruma_client_api::{ state::{create_state_event_for_empty_key, create_state_event_for_key}, sync::sync_events, thirdparty::get_protocols, + typing::create_typing_event, }, unversioned::get_supported_versions, }; @@ -468,7 +469,7 @@ pub fn set_read_marker_route( user_receipts.insert( user_id.clone(), ruma_events::receipt::Receipt { - ts: Some(utils::millis_since_unix_epoch()), + ts: Some(utils::millis_since_unix_epoch().try_into().unwrap()), }, ); let mut receipt_content = HashMap::new(); @@ -491,6 +492,38 @@ pub fn set_read_marker_route( MatrixResult(Ok(set_read_marker::Response)) } +#[put( + "/_matrix/client/r0/rooms/<_room_id>/typing/<_user_id>", + data = "" +)] +pub fn create_typing_event_route( + data: State, + body: Ruma, + _room_id: String, + _user_id: String, +) -> MatrixResult { + let user_id = body.user_id.clone().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 { + data.roomactive_add( + edu, + &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), + ); + } else { + data.roomactive_remove(edu, &body.room_id); + } + + MatrixResult(Ok(create_typing_event::Response)) +} + #[post("/_matrix/client/r0/createRoom", data = "")] pub fn create_room_route( data: State, @@ -745,6 +778,8 @@ pub fn sync_route( for room_id in joined_roomids { let pdus = { data.pdus_since(&room_id, since) }; let room_events = pdus.into_iter().map(|pdu| pdu.to_room_event()).collect(); + let mut edus = data.roomlatests_since(&room_id, since); + edus.extend_from_slice(&data.roomactives_in(&room_id)); joined_rooms.insert( room_id.clone().try_into().unwrap(), @@ -765,9 +800,7 @@ pub fn sync_route( events: room_events, }, state: sync_events::State { events: Vec::new() }, - ephemeral: sync_events::Ephemeral { - events: data.roomlatests_since(&room_id, since), - }, + ephemeral: sync_events::Ephemeral { events: edus }, }, ); } diff --git a/src/data.rs b/src/data.rs index 6d43278..85909f8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -316,7 +316,7 @@ impl Data { room_id: room_id.clone(), sender: sender.clone(), origin: self.hostname.clone(), - origin_server_ts: utils::millis_since_unix_epoch(), + origin_server_ts: utils::millis_since_unix_epoch().try_into().unwrap(), kind: event_type, content, state_key, @@ -415,8 +415,7 @@ impl Data { } pub fn roomlatest_update(&self, user_id: &UserId, room_id: &RoomId, event: EduEvent) { - let mut prefix = vec![b'd']; - prefix.extend_from_slice(room_id.to_string().as_bytes()); + let mut prefix = room_id.to_string().as_bytes().to_vec(); prefix.push(0xff); // Start with last @@ -475,8 +474,7 @@ impl Data { pub fn roomlatests_since(&self, room_id: &RoomId, since: u64) -> Vec { let mut room_latests = Vec::new(); - let mut prefix = vec![b'd']; - prefix.extend_from_slice(room_id.to_string().as_bytes()); + let mut prefix = room_id.to_string().as_bytes().to_vec(); prefix.push(0xff); let mut current = prefix.clone(); @@ -499,6 +497,102 @@ impl Data { room_latests } + pub fn roomactive_add(&self, event: EduEvent, room_id: &RoomId, timeout: u64) { + let mut prefix = room_id.to_string().as_bytes().to_vec(); + prefix.push(0xff); + + let mut current = prefix.clone(); + + while let Some((key, _)) = self.db.roomactiveid_roomactive.get_gt(¤t).unwrap() { + if key.starts_with(&prefix) + && utils::u64_from_bytes(key.split(|&c| c == 0xff).nth(1).unwrap()) + > utils::millis_since_unix_epoch().try_into().unwrap() + { + current = key.to_vec(); + self.db.roomactiveid_roomactive.remove(¤t).unwrap(); + } else { + break; + } + } + + // Increment the last index and use that + let index = utils::u64_from_bytes( + &self + .db + .pduid_pdu + .update_and_fetch(b"n", utils::increment) + .unwrap() + .unwrap(), + ); + + 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(&index.to_be_bytes()); + + self.db + .roomactiveid_roomactive + .insert(room_active_id, &*serde_json::to_string(&event).unwrap()) + .unwrap(); + } + + pub fn roomactive_remove(&self, event: EduEvent, room_id: &RoomId) { + let mut prefix = room_id.to_string().as_bytes().to_vec(); + prefix.push(0xff); + + let mut current = prefix.clone(); + + let json = serde_json::to_string(&event).unwrap(); + + while let Some((key, value)) = self.db.roomactiveid_roomactive.get_gt(¤t).unwrap() { + if key.starts_with(&prefix) { + current = key.to_vec(); + if value == json.as_bytes() { + self.db.roomactiveid_roomactive.remove(¤t).unwrap(); + break; + } + } else { + break; + } + } + } + + /// Returns a vector of the most recent read_receipts in a room that happened after the event with id `since`. + pub fn roomactives_in(&self, room_id: &RoomId) -> Vec { + let mut room_actives = Vec::new(); + + let mut prefix = room_id.to_string().as_bytes().to_vec(); + prefix.push(0xff); + + let mut current = prefix.clone(); + current.extend_from_slice(&utils::millis_since_unix_epoch().to_be_bytes()); + + while let Some((key, value)) = self.db.roomactiveid_roomactive.get_gt(¤t).unwrap() { + if key.starts_with(&prefix) { + current = key.to_vec(); + room_actives.push( + serde_json::from_slice::>(&value) + .expect("room_active in db is valid") + .into_result() + .expect("room_active in db is valid"), + ); + } else { + break; + } + } + + if room_actives.is_empty() { + return vec![EduEvent::Typing(ruma_events::typing::TypingEvent { + content: ruma_events::typing::TypingEventContent { + user_ids: Vec::new(), + }, + room_id: None, // None because it can be inferred + })]; + } else { + room_actives + } + } + pub fn debug(&self) { self.db.debug(); } diff --git a/src/database.rs b/src/database.rs index 18de222..c7a8623 100644 --- a/src/database.rs +++ b/src/database.rs @@ -64,7 +64,7 @@ pub struct Database { pub userid_roomids: MultiValue, // EDUs: pub roomlatestid_roomlatest: sled::Tree, // Read Receipts, RoomLatestId = RoomId + Since + UserId TODO: Types - pub timeofremoval_roomrelevants: MultiValue, // Typing + pub roomactiveid_roomactive: sled::Tree, // Typing, RoomActiveId = TimeoutTime + Since pub globalallid_globalall: sled::Tree, // ToDevice, GlobalAllId = UserId + Since pub globallatestid_globallatest: sled::Tree, // Presence, GlobalLatestId = Since + Type + UserId _db: sled::Db, @@ -103,9 +103,7 @@ impl Database { roomid_userids: MultiValue(db.open_tree("roomid_userids").unwrap()), userid_roomids: MultiValue(db.open_tree("userid_roomids").unwrap()), roomlatestid_roomlatest: db.open_tree("roomlatestid_roomlatest").unwrap(), - timeofremoval_roomrelevants: MultiValue( - db.open_tree("timeofremoval_roomrelevants").unwrap(), - ), + roomactiveid_roomactive: db.open_tree("roomactiveid_roomactive").unwrap(), globalallid_globalall: db.open_tree("globalallid_globalall").unwrap(), globallatestid_globallatest: db.open_tree("globallatestid_globallatest").unwrap(), _db: db, @@ -201,7 +199,7 @@ impl Database { String::from_utf8_lossy(&v), ); } - println!("\n# RoomLatestId -> RoomLatest"); + println!("\n# RoomLatestId -> RoomLatest:"); for (k, v) in self.roomlatestid_roomlatest.iter().map(|r| r.unwrap()) { println!( "{:?} -> {:?}", @@ -209,12 +207,8 @@ impl Database { String::from_utf8_lossy(&v), ); } - println!("\n# TimeOfRemoval -> RoomRelevants Id:"); - for (k, v) in self - .timeofremoval_roomrelevants - .iter_all() - .map(|r| r.unwrap()) - { + println!("\n# RoomActiveId -> RoomActives:"); + for (k, v) in self.roomactiveid_roomactive.iter().map(|r| r.unwrap()) { println!( "{:?} -> {:?}", String::from_utf8_lossy(&k), diff --git a/src/main.rs b/src/main.rs index 5a5eaa0..d09a787 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ fn setup_rocket(data: Data) -> rocket::Rocket { client_server::get_keys_route, client_server::upload_keys_route, client_server::set_read_marker_route, + client_server::create_typing_event_route, client_server::create_room_route, client_server::get_alias_route, client_server::join_room_by_id_route, diff --git a/src/utils.rs b/src/utils.rs index e08e09f..5e94172 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,13 +4,11 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -pub fn millis_since_unix_epoch() -> js_int::UInt { - (SystemTime::now() +pub fn millis_since_unix_epoch() -> u64 { + SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() - .as_millis() as u64) - .try_into() - .expect("time millis are <= MAX_SAFE_UINT") + .as_millis() as u64 } pub fn increment(old: Option<&[u8]>) -> Option> {