From 5ef9a7b924be3dad0812d26c70927da87c740329 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 14:42:22 +0200 Subject: [PATCH 01/83] tests: Rename get_room_id to test_room_id. To make it more obvious it's a special room ID value used in tests. --- matrix_sdk_base/src/models/room_member.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 9834b716..bdfd5721 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -233,7 +233,9 @@ mod test { client } - fn get_room_id() -> RoomId { + // TODO: Move this to EventBuilder since it's a magic room ID used in EventBuilder's example + // events. + fn test_room_id() -> RoomId { RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap() } @@ -241,7 +243,7 @@ mod test { async fn room_member_events() { let client = get_client().await; - let room_id = get_room_id(); + let room_id = test_room_id(); let mut response = EventBuilder::default() .add_room_event(EventsJson::Member, RoomEvent::RoomMember) @@ -264,7 +266,7 @@ mod test { async fn member_presence_events() { let client = get_client().await; - let room_id = get_room_id(); + let room_id = test_room_id(); let mut response = EventBuilder::default() .add_room_event(EventsJson::Member, RoomEvent::RoomMember) From 9bd8699e180046270a7b4bbdc9e9765fb9151945 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 15:01:03 +0200 Subject: [PATCH 02/83] Get rid of match on membership change in RoomMember::update_profile. The calling method already did this when it determined that update_profile should be called so we don't need to repeat it. --- matrix_sdk_base/src/models/room_member.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index bdfd5721..b174bb5d 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -117,18 +117,9 @@ impl RoomMember { /// Handle profile updates. pub(crate) fn update_profile(&mut self, event: &MemberEvent) -> bool { - use MembershipChange::*; - - match event.membership_change() { - ProfileChanged => { - self.display_name = event.content.displayname.clone(); - self.avatar_url = event.content.avatar_url.clone(); - true - } - - // We're only interested in profile changes here. - _ => false, - } + self.display_name = event.content.displayname.clone(); + self.avatar_url = event.content.avatar_url.clone(); + true } pub fn update_power(&mut self, event: &PowerLevelsEvent, max_power: Int) -> bool { From 4561b94f336a039a1d1e319a74b53a28886f5850 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 15:33:38 +0200 Subject: [PATCH 03/83] Remove outdated TODO. --- matrix_sdk_base/src/models/room.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index e7c3f175..ef8d3a1a 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -586,8 +586,6 @@ impl Room { pub fn handle_membership(&mut self, event: &MemberEvent) -> bool { use MembershipChange::*; - // TODO: This would not be handled correctly as all the MemberEvents have the `prev_content` - // inside of `unsigned` field. match event.membership_change() { Invited | Joined => self.add_member(event), Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { From c57f0763759800753a21e852941fa398a01f3860 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 16:16:26 +0200 Subject: [PATCH 04/83] Remove unused import. --- matrix_sdk_base/src/models/room_member.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index b174bb5d..3800fda7 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -18,7 +18,7 @@ use std::convert::TryFrom; use crate::events::collections::all::Event; use crate::events::presence::{PresenceEvent, PresenceEventContent, PresenceState}; use crate::events::room::{ - member::{MemberEvent, MembershipChange}, + member::MemberEvent, power_levels::PowerLevelsEvent, }; use crate::identifiers::UserId; From 84fc66261438a129f197130d59f2012fbfa65d95 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 17:09:10 +0200 Subject: [PATCH 05/83] Document and improve EventBuilder. EventBuilder now clears itself between `build_sync_response` calls so that each subsequent call will return an empty response if nothing was added. This allows reuse of a single EventBuilder instance which is important for correct sync token rotation. --- matrix_sdk_test/src/lib.rs | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index 80bba176..c07c1d33 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -47,7 +47,41 @@ pub enum EventsJson { Typing, } -/// Easily create events to stream into either a Client or a `Room` for testing. +/// The `EventBuilder` struct can be used to easily generate valid sync responses for testing. +/// These can be then fed into either `Client` or `Room`. +/// +/// It supports generated a number of canned events, such as a member entering a room, his power +/// level and display name changing and similar. It also supports insertion of custom events in the +/// form of `EventsJson` values. +/// +/// **Important** You *must* use the *same* builder when sending multiple sync responses to +/// a single client. Otherwise, the subsequent responses will be *ignored* by the client because +/// the `next_batch` sync token will not be rotated properly. +/// +/// # Example usage +/// +/// ```rust +/// use matrix_sdk_test::{EventBuilder, EventsJson}; +/// use matrix_sdk_common::events::collections::all::RoomEvent; +/// +/// let mut builder = EventBuilder::new(); +/// +/// // response1 now contains events that add an example member to the room and change their power +/// // level +/// let response1 = builder +/// .add_room_event(EventsJson::Member, RoomEvent::RoomMember) +/// .add_room_event(EventsJson::PowerLevels, RoomEvent::RoomPowerLevels) +/// .build_sync_response(); +/// +/// // response2 is now empty (nothing changed) +/// let response2 = builder.build_sync_response(); +/// +/// // response3 contains a display name change for member example +/// let response3 = builder +/// .add_room_event(EventsJson::MemberNameChange, RoomEvent::RoomMember) +/// .build_sync_response(); +/// ``` + #[derive(Default)] pub struct EventBuilder { /// The events that determine the state of a `Room`. @@ -226,7 +260,8 @@ impl EventBuilder { self } - /// Consumes `ResponseBuilder` and returns `SyncResponse`. + /// Builds a `SyncResponse` containing the events we queued so far. The next response returned + /// by `build_sync_response` will then be empty if no further events were queued. pub fn build_sync_response(&mut self) -> SyncResponse { let main_room_id = RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap(); @@ -338,12 +373,26 @@ impl EventBuilder { let response = Response::builder() .body(serde_json::to_vec(&body).unwrap()) .unwrap(); + + // Clear state so that the next sync response will be empty if nothing was added. + self.clear(); + SyncResponse::try_from(response).unwrap() } fn generate_sync_token(&self) -> String { format!("t392-516_47314_0_7_1_1_1_11444_{}", self.batch_counter) } + + pub fn clear(&mut self) { + self.account_data.clear(); + self.ephemeral.clear(); + self.invited_room_events.clear(); + self.joined_room_events.clear(); + self.left_room_events.clear(); + self.presence_events.clear(); + self.state_events.clear(); + } } /// Embedded sync reponse files From f447c55fcbcccf7a4e144e948206b27581c8e767 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 30 Jun 2020 19:02:42 +0200 Subject: [PATCH 06/83] Move prev_content in test data to top level for now. Until Ruma fixes it upstream, see hoist_room_event_prev_content for more information. --- matrix_sdk_test/src/test_json/events.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/matrix_sdk_test/src/test_json/events.rs b/matrix_sdk_test/src/test_json/events.rs index a4ba0ced..c3bcea49 100644 --- a/matrix_sdk_test/src/test_json/events.rs +++ b/matrix_sdk_test/src/test_json/events.rs @@ -208,6 +208,7 @@ lazy_static! { }); } +// TODO: Move `prev_content` into `unsigned` once ruma supports it lazy_static! { pub static ref MEMBER: JsonValue = json!({ "content": { @@ -221,14 +222,14 @@ lazy_static! { "sender": "@example:localhost", "state_key": "@example:localhost", "type": "m.room.member", + "prev_content": { + "avatar_url": null, + "displayname": "example", + "membership": "invite" + }, "unsigned": { "age": 297036, - "replaces_state": "$151800111315tsynI:localhost", - "prev_content": { - "avatar_url": null, - "displayname": "example", - "membership": "invite" - } + "replaces_state": "$151800111315tsynI:localhost" } }); } @@ -552,6 +553,7 @@ lazy_static! { }); } +// TODO: Move `prev_content` into `unsigned` once ruma supports it lazy_static! { pub static ref TOPIC: JsonValue = json!({ "content": { @@ -562,11 +564,11 @@ lazy_static! { "sender": "@example:localhost", "state_key": "", "type": "m.room.topic", + "prev_content": { + "topic": "test" + }, "unsigned": { "age": 1392989, - "prev_content": { - "topic": "test" - }, "prev_sender": "@example:localhost", "replaces_state": "$151957069225EVYKm:localhost" } From 2a0c6c6474a130c55ed9d6cbbcde26ef20e42f6b Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 14:50:42 +0200 Subject: [PATCH 07/83] Add test and example event to ensure display name changes work correctly. --- matrix_sdk_base/src/models/room_member.rs | 44 +++++++++++++++++++++++ matrix_sdk_test/src/lib.rs | 2 ++ matrix_sdk_test/src/test_json/events.rs | 26 ++++++++++++++ matrix_sdk_test/src/test_json/mod.rs | 4 +-- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 3800fda7..0db41cb3 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -253,6 +253,50 @@ mod test { assert_eq!(member.power_level, Int::new(100)); } + #[async_test] + async fn room_member_display_name_change() { + let client = get_client().await; + let room_id = test_room_id(); + + let mut builder = EventBuilder::default(); + let mut initial_response = builder + .add_room_event(EventsJson::Member, RoomEvent::RoomMember) + .build_sync_response(); + let mut name_change_response = builder + .add_room_event(EventsJson::MemberNameChange, RoomEvent::RoomMember) + .build_sync_response(); + + client.receive_sync_response(&mut initial_response).await.unwrap(); + + let room = client.get_joined_room(&room_id).await.unwrap(); + + // Initially, the display name is "example". + { + let room = room.read().await; + + let member = room + .joined_members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); + + assert_eq!(member.display_name.as_ref().unwrap(), "example"); + } + + client.receive_sync_response(&mut name_change_response).await.unwrap(); + + // Afterwards, the display name is "changed". + { + let room = room.read().await; + + let member = room + .joined_members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); + + assert_eq!(member.display_name.as_ref().unwrap(), "changed"); + } + } + #[async_test] async fn member_presence_events() { let client = get_client().await; diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index c07c1d33..2cfa88c7 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -31,6 +31,7 @@ pub enum EventsJson { HistoryVisibility, JoinRules, Member, + MemberNameChange, MessageEmote, MessageNotice, MessageText, @@ -154,6 +155,7 @@ impl EventBuilder { ) -> &mut Self { let val: &JsonValue = match json { EventsJson::Member => &test_json::MEMBER, + EventsJson::MemberNameChange => &test_json::MEMBER_NAME_CHANGE, EventsJson::PowerLevels => &test_json::POWER_LEVELS, _ => panic!("unknown room event json {:?}", json), }; diff --git a/matrix_sdk_test/src/test_json/events.rs b/matrix_sdk_test/src/test_json/events.rs index c3bcea49..da3bc5a4 100644 --- a/matrix_sdk_test/src/test_json/events.rs +++ b/matrix_sdk_test/src/test_json/events.rs @@ -234,6 +234,32 @@ lazy_static! { }); } +// TODO: Move `prev_content` into `unsigned` once ruma supports it +lazy_static! { + pub static ref MEMBER_NAME_CHANGE: JsonValue = json!({ + "content": { + "avatar_url": null, + "displayname": "changed", + "membership": "join" + }, + "event_id": "$151800234427abgho:localhost", + "membership": "join", + "origin_server_ts": 151800152, + "sender": "@example:localhost", + "state_key": "@example:localhost", + "type": "m.room.member", + "prev_content": { + "avatar_url": null, + "displayname": "example", + "membership": "join" + }, + "unsigned": { + "age": 297032, + "replaces_state": "$151800140517rfvjc:localhost" + } + }); +} + lazy_static! { pub static ref MESSAGE_EDIT: JsonValue = json!({ "content": { diff --git a/matrix_sdk_test/src/test_json/mod.rs b/matrix_sdk_test/src/test_json/mod.rs index 406f2023..d17f13bd 100644 --- a/matrix_sdk_test/src/test_json/mod.rs +++ b/matrix_sdk_test/src/test_json/mod.rs @@ -9,7 +9,7 @@ pub mod sync; pub use events::{ ALIAS, ALIASES, EVENT_ID, KEYS_QUERY, KEYS_UPLOAD, LOGIN, LOGIN_RESPONSE_ERR, LOGOUT, MEMBER, - MESSAGE_EDIT, MESSAGE_TEXT, NAME, POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, REACTION, - REGISTRATION_RESPONSE_ERR, ROOM_ID, ROOM_MESSAGES, TYPING, + MEMBER_NAME_CHANGE, MESSAGE_EDIT, MESSAGE_TEXT, NAME, POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, + REACTION, REGISTRATION_RESPONSE_ERR, ROOM_ID, ROOM_MESSAGES, TYPING, }; pub use sync::{DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC}; From ff5f638b60292cd907390eb5e3f163cda283a1a7 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 14:53:58 +0200 Subject: [PATCH 08/83] Remove member from invited_members when he joins. --- matrix_sdk_base/src/models/room.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index ef8d3a1a..1602c3d5 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -363,19 +363,22 @@ impl Room { fn add_member(&mut self, event: &MemberEvent) -> bool { let new_member = RoomMember::new(event); - if self.joined_members.contains_key(&new_member.user_id) - || self.invited_members.contains_key(&new_member.user_id) - { - return false; - } - match event.membership_change() { - MembershipChange::Joined => self - .joined_members - .insert(new_member.user_id.clone(), new_member.clone()), + MembershipChange::Joined => { + // Since the member is now joined, he shouldn't be tracked as an invited member any + // longer. + if self.invited_members.contains_key(&new_member.user_id) { + self.invited_members.remove(&new_member.user_id); + } + + self.joined_members + .insert(new_member.user_id.clone(), new_member.clone()) + } + MembershipChange::Invited => self .invited_members .insert(new_member.user_id.clone(), new_member.clone()), + _ => { panic!("Room::add_member called on an event that is neither a join nor an invite.") } From 9af48920f66dc545075d734b9e7e4fc13b256039 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 10:39:45 +0200 Subject: [PATCH 09/83] Add some TODOs and FIXMEs. --- matrix_sdk_base/src/models/room_member.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 0db41cb3..3b7639c1 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -25,6 +25,7 @@ use crate::identifiers::UserId; use crate::js_int::{Int, UInt}; use serde::{Deserialize, Serialize}; + // Notes: if Alice invites Bob into a room we will get an event with the sender as Alice and the state key as Bob. #[derive(Debug, Serialize, Deserialize, Clone)] @@ -55,6 +56,14 @@ pub struct RoomMember { pub power_level_norm: Option, /// The human readable name of this room member. pub name: String, + // FIXME: The docstring below is currently a lie since we only store the initial event that + // creates the member (the one we pass to RoomMember::new). + // + // The intent of this field is to keep the last (or last few?) state events related to the room + // member cached so we can quickly go back to the previous one in case some of them get + // redacted. Keeping all state for each room member is probably too much. + // + // Needs design. /// The events that created the state of this room member. #[serde(deserialize_with = "super::event_deser::deserialize_events")] pub events: Vec, @@ -116,6 +125,7 @@ impl RoomMember { } /// Handle profile updates. + // TODO: NEXT: Add disambiguation handling here pub(crate) fn update_profile(&mut self, event: &MemberEvent) -> bool { self.display_name = event.content.displayname.clone(); self.avatar_url = event.content.avatar_url.clone(); From 32bdcede0caf1813a25e2f2ba6f086dc156b07bb Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 12:06:59 +0200 Subject: [PATCH 10/83] Small refactoring to simplify member_disambiguations. --- matrix_sdk_base/src/models/room.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 1602c3d5..b2547f76 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -427,7 +427,7 @@ impl Room { /// /// The `inclusive` parameter controls whether the passed member should be included in the /// list or not. - fn shares_displayname_with(&self, member: &RoomMember, inclusive: bool) -> Vec { + fn shares_displayname_with(&self, member: &RoomMember, inclusive: bool) -> Vec { let members = self .invited_members .iter() @@ -449,7 +449,7 @@ impl Room { }) // If not an inclusive search, do not consider the member for which we are disambiguating. .filter(|(id, _)| inclusive || **id != member.user_id) - .map(|(id, _)| id) + .map(|(_, member)| member) .cloned() .collect() } @@ -467,16 +467,10 @@ impl Room { inclusive: bool, ) -> HashMap { let users_with_same_name = self.shares_displayname_with(member, inclusive); - let disambiguate_with = |members: Vec, f: fn(&RoomMember) -> String| { + let disambiguate_with = |members: Vec, f: fn(&RoomMember) -> String| { members .into_iter() - .filter_map(|id| { - self.joined_members - .get(&id) - .or_else(|| self.invited_members.get(&id)) - .map(f) - .map(|m| (id, m)) - }) + .map(|ref m| (m.user_id.clone(), f(m))) .collect::>() }; From c2ec69cf44eabc236ede959941fbdf8ef3c6a66a Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 13:48:31 +0200 Subject: [PATCH 11/83] Style fixes (comment grammar and correctness, whitespace). --- matrix_sdk_base/src/models/room_member.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 3b7639c1..71e59195 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -129,6 +129,7 @@ impl RoomMember { pub(crate) fn update_profile(&mut self, event: &MemberEvent) -> bool { self.display_name = event.content.displayname.clone(); self.avatar_url = event.content.avatar_url.clone(); + true } @@ -149,13 +150,13 @@ impl RoomMember { changed } - /// If the current `PresenceEvent` updated the state of this `User`. + /// If the current `PresenceEvent` updated the state of this `RoomMember`. /// - /// Returns true if the specific users presence has changed, false otherwise. + /// Returns true if the member's presence has changed, false otherwise. /// /// # Arguments /// - /// * `presence` - The presence event for a this room member. + /// * `presence` - The presence event for this room member. pub fn did_update_presence(&self, presence: &PresenceEvent) -> bool { let PresenceEvent { content: @@ -177,13 +178,13 @@ impl RoomMember { && self.currently_active == *currently_active } - /// Updates the `User`s presence. + /// Updates the `RoomMember`'s presence. /// /// This should only be used if `did_update_presence` was true. /// /// # Arguments /// - /// * `presence` - The presence event for a this room member. + /// * `presence` - The presence event for this room member. pub fn update_presence(&mut self, presence_ev: &PresenceEvent) { let PresenceEvent { content: From 599c1ba98f4f36f939db7a35888b5ee0cdc39fb7 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 13:51:21 +0200 Subject: [PATCH 12/83] Add test to ensure member is only treated as joined or invited, not both. --- matrix_sdk_base/src/models/room.rs | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index b2547f76..060ebb2a 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -831,6 +831,114 @@ mod test { assert!(room.deref().power_levels.is_some()) } + #[async_test] + async fn member_is_not_both_invited_and_joined() { + let client = get_client().await; + let room_id = get_room_id(); + let user_id1 = UserId::try_from("@example:localhost").unwrap(); + let user_id2 = UserId::try_from("@example2:localhost").unwrap(); + + let member2_invite_event = serde_json::json!({ + "content": { + "avatar_url": null, + "displayname": "example2", + "membership": "invite" + }, + "event_id": "$16345217l517tabbz:localhost", + "membership": "join", + "origin_server_ts": 1455123234, + "sender": format!("{}", user_id1), + "state_key": format!("{}", user_id2), + "type": "m.room.member", + "unsigned": { + "age": 1989321234, + "replaces_state": "$1622a2311315tkjoA:localhost" + } + }); + + let member2_join_event = serde_json::json!({ + "content": { + "avatar_url": null, + "displayname": "example2", + "membership": "join" + }, + "event_id": "$163409224327jkbba:localhost", + "membership": "join", + "origin_server_ts": 1455123238, + "sender": format!("{}", user_id2), + "state_key": format!("{}", user_id2), + "type": "m.room.member", + "prev_content": { + "avatar_url": null, + "displayname": "example2", + "membership": "invite" + }, + "unsigned": { + "age": 1989321214, + "replaces_state": "$16345217l517tabbz:localhost" + } + }); + + let mut event_builder = EventBuilder::new(); + + let mut member1_join_sync_response = event_builder + .add_room_event(EventsJson::Member, RoomEvent::RoomMember) + .build_sync_response(); + + let mut member2_invite_sync_response = event_builder + .add_custom_joined_event(&room_id, member2_invite_event, RoomEvent::RoomMember) + .build_sync_response(); + + let mut member2_join_sync_response = event_builder + .add_custom_joined_event(&room_id, member2_join_event, RoomEvent::RoomMember) + .build_sync_response(); + + + // Test that `user` is either joined or invited to `room` but not both. + async fn invited_or_joined_but_not_both(client: &BaseClient, room: &RoomId, user: &UserId) { + let room = client.get_joined_room(&room).await.unwrap(); + let room = room.read().await; + + assert!( + room.invited_members.get(&user).is_none() + || room.joined_members.get(&user).is_none() + ); + assert!( + room.invited_members.get(&user).is_some() + || room.joined_members.get(&user).is_some() + ); + }; + + // First member joins. + client + .receive_sync_response(&mut member1_join_sync_response) + .await + .unwrap(); + + // The first member is not *both* invited and joined but it *is* one of those. + invited_or_joined_but_not_both(&client, &room_id, &user_id1).await; + + // First member invites second member. + client + .receive_sync_response(&mut member2_invite_sync_response) + .await + .unwrap(); + + // Neither member is *both* invited and joined, but they are both *at least one* of those. + invited_or_joined_but_not_both(&client, &room_id, &user_id1).await; + invited_or_joined_but_not_both(&client, &room_id, &user_id2).await; + + // Second member joins. + client + .receive_sync_response(&mut member2_join_sync_response) + .await + .unwrap(); + + // Repeat the previous test. + invited_or_joined_but_not_both(&client, &room_id, &user_id1).await; + invited_or_joined_but_not_both(&client, &room_id, &user_id2).await; + } + #[async_test] async fn test_member_display_name() { // Initialize From 6cacf836616969ce7016a97abbe012b7c0edb848 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 13:53:10 +0200 Subject: [PATCH 13/83] Add (failing) test for displayname disambiguation on profile updates. --- matrix_sdk_base/src/models/room.rs | 119 ++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 9 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 060ebb2a..6bc060a4 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -972,6 +972,47 @@ mod test { } }); + let member1_invites_member2_event = serde_json::json!({ + "content": { + "avatar_url": null, + "displayname": "example", + "membership": "invite" + }, + "event_id": "$16345217l517tabbz:localhost", + "membership": "invite", + "origin_server_ts": 1455123238, + "sender": format!("{}", user_id1), + "state_key": format!("{}", user_id2), + "type": "m.room.member", + "unsigned": { + "age": 1989321238, + "replaces_state": "$1622a2311315tkjoA:localhost" + } + }); + + let member2_name_change_event = serde_json::json!({ + "content": { + "avatar_url": null, + "displayname": "changed", + "membership": "join" + }, + "event_id": "$16345217l517tabbz:localhost", + "membership": "join", + "origin_server_ts": 1455123238, + "sender": format!("{}", user_id2), + "state_key": format!("{}", user_id2), + "type": "m.room.member", + "prev_content": { + "avatar_url": null, + "displayname": "example", + "membership": "join" + }, + "unsigned": { + "age": 1989321238, + "replaces_state": "$1622a2311315tkjoA:localhost" + } + }); + let member2_leave_event = serde_json::json!({ "content": { "avatar_url": null, @@ -1048,19 +1089,29 @@ mod test { .build_sync_response(); let mut member2_join_sync_response = event_builder - .add_custom_joined_event(&room_id, member2_join_event, RoomEvent::RoomMember) + .add_custom_joined_event(&room_id, member2_join_event.clone(), RoomEvent::RoomMember) .build_sync_response(); let mut member3_join_sync_response = event_builder .add_custom_joined_event(&room_id, member3_join_event, RoomEvent::RoomMember) .build_sync_response(); - let mut member2_leave_sync_response = event_builder + let mut member2_and_member3_leave_sync_response = event_builder .add_custom_joined_event(&room_id, member2_leave_event, RoomEvent::RoomMember) + .add_custom_joined_event(&room_id, member3_leave_event, RoomEvent::RoomMember) .build_sync_response(); - let mut member3_leave_sync_response = event_builder - .add_custom_joined_event(&room_id, member3_leave_event, RoomEvent::RoomMember) + let mut member2_rejoins_when_invited_sync_response = event_builder + .add_custom_joined_event(&room_id, member1_invites_member2_event, RoomEvent::RoomMember) + .add_custom_joined_event(&room_id, member2_join_event, RoomEvent::RoomMember) + .build_sync_response(); + + let mut member1_name_change_sync_response = event_builder + .add_room_event(EventsJson::MemberNameChange, RoomEvent::RoomMember) + .build_sync_response(); + + let mut member2_name_change_sync_response = event_builder + .add_custom_joined_event(&room_id, member2_name_change_event, RoomEvent::RoomMember) .build_sync_response(); // First member with display name "example" joins @@ -1103,11 +1154,7 @@ mod test { // Second and third member leave. The first's display name is now just "example" again. client - .receive_sync_response(&mut member2_leave_sync_response) - .await - .unwrap(); - client - .receive_sync_response(&mut member3_leave_sync_response) + .receive_sync_response(&mut member2_and_member3_leave_sync_response) .await .unwrap(); @@ -1119,6 +1166,60 @@ mod test { assert_eq!("example", display_name1); } + + // Second member rejoins after being invited by first member. Both of their names are + // disambiguated. + client + .receive_sync_response(&mut member2_rejoins_when_invited_sync_response) + .await + .unwrap(); + + { + let room = client.get_joined_room(&room_id).await.unwrap(); + let room = room.read().await; + + let display_name1 = room.member_display_name(&user_id1); + let display_name2 = room.member_display_name(&user_id2); + + assert_eq!(format!("example ({})", user_id1), display_name1); + assert_eq!(format!("example ({})", user_id2), display_name2); + } + + // First member changes his display name to "changed". None of the display names are + // disambiguated. + client + .receive_sync_response(&mut member1_name_change_sync_response) + .await + .unwrap(); + + { + let room = client.get_joined_room(&room_id).await.unwrap(); + let room = room.read().await; + + let display_name1 = room.member_display_name(&user_id1); + let display_name2 = room.member_display_name(&user_id2); + + assert_eq!("changed", display_name1); + assert_eq!("example", display_name2); + } + + // Second member *also* changes his display name to "changed". Again, both display name are + // disambiguated. + client + .receive_sync_response(&mut member2_name_change_sync_response) + .await + .unwrap(); + + { + let room = client.get_joined_room(&room_id).await.unwrap(); + let room = room.read().await; + + let display_name1 = room.member_display_name(&user_id1); + let display_name2 = room.member_display_name(&user_id2); + + assert_eq!(format!("changed ({})", user_id1), display_name1); + assert_eq!(format!("changed ({})", user_id2), display_name2); + } } #[async_test] From 5f49dab1fa046c42c1cd8de8c2b23dd75cbaff1d Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 14:02:37 +0200 Subject: [PATCH 14/83] Correct docstring. --- matrix_sdk_base/src/models/room.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 6bc060a4..c2181294 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -763,7 +763,7 @@ impl Room { /// /// # Arguments /// - /// * `event` - The presence event for a specified room member. + /// * `event` - The presence event to receive and process. pub fn receive_presence_event(&mut self, event: &PresenceEvent) -> bool { if let Some(member) = self.joined_members.get_mut(&event.sender) { if member.did_update_presence(event) { From eeebb43e321361319733b881060cc9c0d73e8820 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 15:44:44 +0200 Subject: [PATCH 15/83] Move mutating methods from RoomMember to Room. The `update_profile` method cannot live in `RoomMember` since that operation needs information which only exists in `Room` (for instance, it needs other members in order to perform display name disambiguation). Leaving other mutating methods on `RoomMember` (like `update_power` and `update_presence`) then seemed illogical so they were all moved into `Room`. In addition, a small refactoring was done to remove `did_update_presence` and `update_presence` since their existence doesn't make much sense anymore and it saves us from repeating work. Their function is now done in `receive_presence_event`. Also, several docstrings were corrected and reworded. --- matrix_sdk_base/src/models/room.rs | 115 +++++++++++++++++----- matrix_sdk_base/src/models/room_member.rs | 91 +---------------- 2 files changed, 95 insertions(+), 111 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index c2181294..1dae019d 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -23,7 +23,7 @@ use super::RoomMember; use crate::api::r0::sync::sync_events::{RoomSummary, UnreadNotificationsCount}; use crate::events::collections::all::{RoomEvent, StateEvent}; -use crate::events::presence::PresenceEvent; +use crate::events::presence::{PresenceEvent, PresenceEventContent}; use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, @@ -584,22 +584,14 @@ impl Room { use MembershipChange::*; match event.membership_change() { - Invited | Joined => self.add_member(event), + Invited | Joined => { + self.add_member(event) + } Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { self.remove_member(event) } ProfileChanged => { - let user_id = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { - id - } else { - return false; - }; - - if let Some(member) = self.joined_members.get_mut(&user_id) { - member.update_profile(event) - } else { - false - } + self.update_member_profile(event) } // Not interested in other events. @@ -671,11 +663,12 @@ impl Room { for user in event.content.users.keys() { if let Some(member) = self.joined_members.get_mut(user) { - if member.update_power(event, max_power) { + if Room::update_member_power(member, event, max_power) { updated = true; } } } + updated } @@ -755,28 +748,106 @@ impl Room { } } - /// Receive a presence event from an `IncomingResponse` and updates the client state. + /// Receive a presence event for a member of the current room. /// - /// This will only update the user if found in the current room looped through - /// by `Client::sync`. - /// Returns true if the specific users presence has changed, false otherwise. + /// Returns true if the event causes a change to the member's presence, false otherwise. /// /// # Arguments /// /// * `event` - The presence event to receive and process. pub fn receive_presence_event(&mut self, event: &PresenceEvent) -> bool { + let PresenceEvent { + content: + PresenceEventContent { + avatar_url, + currently_active, + displayname, + last_active_ago, + presence, + status_msg, + }, + .. + } = event; + if let Some(member) = self.joined_members.get_mut(&event.sender) { - if member.did_update_presence(event) { + if member.display_name == *displayname + && member.avatar_url == *avatar_url + && member.presence.as_ref() == Some(presence) + && member.status_msg == *status_msg + && member.last_active_ago == *last_active_ago + && member.currently_active == *currently_active { + + // Everything is the same, nothing to do. false } else { - member.update_presence(event); + // Something changed, do the update. + + member.presence_events.push(event.clone()); + member.avatar_url = avatar_url.clone(); + member.currently_active = *currently_active; + member.display_name = displayname.clone(); + member.last_active_ago = *last_active_ago; + member.presence = Some(*presence); + member.status_msg = status_msg.clone(); + true } } else { - // this is probably an error as we have a `PresenceEvent` for a user - // we don't know about + // This is probably an error as we have a `PresenceEvent` for a user + // we don't know about. false } + + } + + /// Process an update of a member's profile. + /// + /// # Arguments + /// + /// * `event` - The profile update event for a specified room member. + // TODO: NEXT: Add disambiguation handling here + pub(crate) fn update_member_profile(&mut self, event: &MemberEvent) -> bool { + let user_id = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { + id + } else { + return false; + }; + + if let Some(member) = self.joined_members.get_mut(&user_id) { + member.display_name = event.content.displayname.clone(); + member.avatar_url = event.content.avatar_url.clone(); + true + } else if let Some(member) = self.invited_members.get_mut(&user_id) { + member.display_name = event.content.displayname.clone(); + member.avatar_url = event.content.avatar_url.clone(); + true + } else { + false + } + } + + /// Process an update of a member's power level. + /// + /// # Arguments + /// + /// * `event` - The power level event to process. + /// * `max_power` - Maximum power level allowed. + pub fn update_member_power(member: &mut RoomMember, event: &PowerLevelsEvent, max_power: Int) -> bool { + let changed; + + if let Some(user_power) = event.content.users.get(&member.user_id) { + changed = member.power_level != Some(*user_power); + member.power_level = Some(*user_power); + } else { + changed = member.power_level != Some(event.content.users_default); + member.power_level = Some(event.content.users_default); + } + + if max_power > Int::from(0) { + member.power_level_norm = Some((member.power_level.unwrap() * Int::from(100)) / max_power); + } + + changed } } diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 71e59195..ced14760 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -16,11 +16,8 @@ use std::convert::TryFrom; use crate::events::collections::all::Event; -use crate::events::presence::{PresenceEvent, PresenceEventContent, PresenceState}; -use crate::events::room::{ - member::MemberEvent, - power_levels::PowerLevelsEvent, -}; +use crate::events::presence::{PresenceEvent, PresenceState}; +use crate::events::room::member::MemberEvent; use crate::identifiers::UserId; use crate::js_int::{Int, UInt}; @@ -123,90 +120,6 @@ impl RoomMember { .map(|d| format!("{} ({})", d, self.user_id)) .unwrap_or_else(|| format!("{}", self.user_id)) } - - /// Handle profile updates. - // TODO: NEXT: Add disambiguation handling here - pub(crate) fn update_profile(&mut self, event: &MemberEvent) -> bool { - self.display_name = event.content.displayname.clone(); - self.avatar_url = event.content.avatar_url.clone(); - - true - } - - pub fn update_power(&mut self, event: &PowerLevelsEvent, max_power: Int) -> bool { - let changed; - if let Some(user_power) = event.content.users.get(&self.user_id) { - changed = self.power_level != Some(*user_power); - self.power_level = Some(*user_power); - } else { - changed = self.power_level != Some(event.content.users_default); - self.power_level = Some(event.content.users_default); - } - - if max_power > Int::from(0) { - self.power_level_norm = Some((self.power_level.unwrap() * Int::from(100)) / max_power); - } - - changed - } - - /// If the current `PresenceEvent` updated the state of this `RoomMember`. - /// - /// Returns true if the member's presence has changed, false otherwise. - /// - /// # Arguments - /// - /// * `presence` - The presence event for this room member. - pub fn did_update_presence(&self, presence: &PresenceEvent) -> bool { - let PresenceEvent { - content: - PresenceEventContent { - avatar_url, - currently_active, - displayname, - last_active_ago, - presence, - status_msg, - }, - .. - } = presence; - self.display_name == *displayname - && self.avatar_url == *avatar_url - && self.presence.as_ref() == Some(presence) - && self.status_msg == *status_msg - && self.last_active_ago == *last_active_ago - && self.currently_active == *currently_active - } - - /// Updates the `RoomMember`'s presence. - /// - /// This should only be used if `did_update_presence` was true. - /// - /// # Arguments - /// - /// * `presence` - The presence event for this room member. - pub fn update_presence(&mut self, presence_ev: &PresenceEvent) { - let PresenceEvent { - content: - PresenceEventContent { - avatar_url, - currently_active, - displayname, - last_active_ago, - presence, - status_msg, - }, - .. - } = presence_ev; - - self.presence_events.push(presence_ev.clone()); - self.avatar_url = avatar_url.clone(); - self.currently_active = *currently_active; - self.display_name = displayname.clone(); - self.last_active_ago = *last_active_ago; - self.presence = Some(*presence); - self.status_msg = status_msg.clone(); - } } #[cfg(test)] From 7abdeed449af37889043a81b4ba5463425ab3b0f Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 15:56:29 +0200 Subject: [PATCH 16/83] fix: Don't issue a disambiguation in case of a unique display name. --- matrix_sdk_base/src/models/room.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 1dae019d..ea171660 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -475,8 +475,7 @@ impl Room { }; match users_with_same_name.len() { - 0 => HashMap::new(), - 1 => disambiguate_with(users_with_same_name, |m: &RoomMember| m.name()), + 0 | 1 => HashMap::new(), _ => disambiguate_with(users_with_same_name, |m: &RoomMember| m.unique_name()), } } From 7943baee49c578141ea5d2ff9313ca08d5569672 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 1 Jul 2020 16:08:25 +0200 Subject: [PATCH 17/83] add_member provably always returns true. --- matrix_sdk_base/src/models/room.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index ea171660..e37c0dec 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -358,9 +358,7 @@ impl Room { } /// Process the member event of an entering user. - /// - /// Returns true if this made a change to the room's state, false otherwise. - fn add_member(&mut self, event: &MemberEvent) -> bool { + fn add_member(&mut self, event: &MemberEvent) { let new_member = RoomMember::new(event); match event.membership_change() { @@ -385,15 +383,15 @@ impl Room { }; // Perform display name disambiguations, if necessary. - let disambiguations = self.disambiguation_updates(&new_member, MemberDirection::Entering); + let disambiguations = + self.disambiguation_updates(&new_member, MemberDirection::Entering); + for (id, name) in disambiguations.into_iter() { match name { None => self.disambiguated_display_names.remove(&id), Some(name) => self.disambiguated_display_names.insert(id, name), }; } - - true } /// Process the member event of a leaving user. @@ -584,7 +582,9 @@ impl Room { match event.membership_change() { Invited | Joined => { - self.add_member(event) + self.add_member(event); + + true } Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { self.remove_member(event) From e70929317ae0fa49f987bc1610cc8532846cc08e Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 10 Jul 2020 15:07:17 +0200 Subject: [PATCH 18/83] Revert "add_member provably always returns true." This reverts commit 7943baee49c578141ea5d2ff9313ca08d5569672. --- matrix_sdk_base/src/models/room.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index e37c0dec..ea171660 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -358,7 +358,9 @@ impl Room { } /// Process the member event of an entering user. - fn add_member(&mut self, event: &MemberEvent) { + /// + /// Returns true if this made a change to the room's state, false otherwise. + fn add_member(&mut self, event: &MemberEvent) -> bool { let new_member = RoomMember::new(event); match event.membership_change() { @@ -383,15 +385,15 @@ impl Room { }; // Perform display name disambiguations, if necessary. - let disambiguations = - self.disambiguation_updates(&new_member, MemberDirection::Entering); - + let disambiguations = self.disambiguation_updates(&new_member, MemberDirection::Entering); for (id, name) in disambiguations.into_iter() { match name { None => self.disambiguated_display_names.remove(&id), Some(name) => self.disambiguated_display_names.insert(id, name), }; } + + true } /// Process the member event of a leaving user. @@ -582,9 +584,7 @@ impl Room { match event.membership_change() { Invited | Joined => { - self.add_member(event); - - true + self.add_member(event) } Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { self.remove_member(event) From 24d2aa80784fedbc2354cc7661037eba3d1311af Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 10 Jul 2020 11:14:10 +0200 Subject: [PATCH 19/83] Style (cargo fmt, reordering import). --- matrix_sdk_base/src/models/room.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index ea171660..0ca03459 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -17,6 +17,8 @@ use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; +use serde::{Deserialize, Serialize}; + #[cfg(feature = "messages")] use super::message::MessageQueue; use super::RoomMember; @@ -42,7 +44,7 @@ use crate::events::room::message::MessageEvent; use crate::identifiers::{RoomAliasId, RoomId, UserId}; use crate::js_int::{Int, UInt}; -use serde::{Deserialize, Serialize}; + #[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)] /// `RoomName` allows the calculation of a text room name. pub struct RoomName { @@ -467,6 +469,7 @@ impl Room { inclusive: bool, ) -> HashMap { let users_with_same_name = self.shares_displayname_with(member, inclusive); + let disambiguate_with = |members: Vec, f: fn(&RoomMember) -> String| { members .into_iter() @@ -831,7 +834,11 @@ impl Room { /// /// * `event` - The power level event to process. /// * `max_power` - Maximum power level allowed. - pub fn update_member_power(member: &mut RoomMember, event: &PowerLevelsEvent, max_power: Int) -> bool { + pub fn update_member_power( + member: &mut RoomMember, + event: &PowerLevelsEvent, + max_power: Int, + ) -> bool { let changed; if let Some(user_power) = event.content.users.get(&member.user_id) { @@ -843,7 +850,8 @@ impl Room { } if max_power > Int::from(0) { - member.power_level_norm = Some((member.power_level.unwrap() * Int::from(100)) / max_power); + member.power_level_norm = + Some((member.power_level.unwrap() * Int::from(100)) / max_power); } changed @@ -963,7 +971,6 @@ mod test { .add_custom_joined_event(&room_id, member2_join_event, RoomEvent::RoomMember) .build_sync_response(); - // Test that `user` is either joined or invited to `room` but not both. async fn invited_or_joined_but_not_both(client: &BaseClient, room: &RoomId, user: &UserId) { let room = client.get_joined_room(&room).await.unwrap(); @@ -1172,7 +1179,11 @@ mod test { .build_sync_response(); let mut member2_rejoins_when_invited_sync_response = event_builder - .add_custom_joined_event(&room_id, member1_invites_member2_event, RoomEvent::RoomMember) + .add_custom_joined_event( + &room_id, + member1_invites_member2_event, + RoomEvent::RoomMember, + ) .add_custom_joined_event(&room_id, member2_join_event, RoomEvent::RoomMember) .build_sync_response(); From 559306a33c49cd3bf13e552a6e43231d64ca1481 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 10 Jul 2020 11:14:22 +0200 Subject: [PATCH 20/83] Rewrite disambiguation algorithm to handle profile changes. The new algorithm is simpler. Instead of tracking a list of disambiguated display names in `Room`, we instead track the display name ambiguity status in `RoomMember`. This allows a client to generate the correct name for a member using solely the information available in `RoomMember`. The disambiguation algorithm itself now only calculates the set of members whose ambiguity status had changed instead of producing disambiguated display names for everyone affected. This is called on each room entry (join or invite), room entry and profile change, and the updates are propagated to the affected `RoomMember`s. --- matrix_sdk_base/Cargo.toml | 1 + matrix_sdk_base/src/client.rs | 4 +- matrix_sdk_base/src/models/message.rs | 2 - matrix_sdk_base/src/models/room.rs | 531 ++++++++++++++-------- matrix_sdk_base/src/models/room_member.rs | 4 + matrix_sdk_base/src/state/mod.rs | 2 - 6 files changed, 351 insertions(+), 193 deletions(-) diff --git a/matrix_sdk_base/Cargo.toml b/matrix_sdk_base/Cargo.toml index 1acb56b7..bf884319 100644 --- a/matrix_sdk_base/Cargo.toml +++ b/matrix_sdk_base/Cargo.toml @@ -21,6 +21,7 @@ async-trait = "0.1.31" serde = "1.0.110" serde_json = "1.0.53" zeroize = "1.1.0" +tracing = "0.1.14" matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_macros" } matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 975c7c6d..2e0bad13 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -734,7 +734,7 @@ impl BaseClient { let mut room = room_lock.write().await; if let RoomEvent::RoomMember(mem_event) = &mut e { - let changed = room.handle_membership(mem_event); + let (changed, _) = room.handle_membership(mem_event); // The memberlist of the room changed, invalidate the group session // of the room. @@ -771,7 +771,7 @@ impl BaseClient { let mut room = room_lock.write().await; if let StateEvent::RoomMember(e) = event { - let changed = room.handle_membership(e); + let (changed, _) = room.handle_membership(e); // The memberlist of the room changed, invalidate the group session // of the room. diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index b78ea1a7..db6d3ac5 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -178,7 +178,6 @@ mod test { serde_json::json!({ "!roomid:example.com": { "room_id": "!roomid:example.com", - "disambiguated_display_names": {}, "room_name": { "name": null, "canonical_alias": null, @@ -229,7 +228,6 @@ mod test { let json = serde_json::json!({ "!roomid:example.com": { "room_id": "!roomid:example.com", - "disambiguated_display_names": {}, "room_name": { "name": null, "canonical_alias": null, diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 0ca03459..61c23a98 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -14,10 +14,11 @@ // limitations under the License. use std::borrow::Cow; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use serde::{Deserialize, Serialize}; +use tracing::{debug, error, trace}; #[cfg(feature = "messages")] use super::message::MessageQueue; @@ -30,7 +31,7 @@ use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, - member::{MemberEvent, MembershipChange}, + member::{MemberEvent, MembershipChange, MembershipState}, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, @@ -146,12 +147,6 @@ pub struct Tombstone { replacement: RoomId, } -#[derive(Debug, PartialEq, Eq)] -enum MemberDirection { - Entering, - Exiting, -} - #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] /// A Matrix room. pub struct Room { @@ -188,8 +183,6 @@ pub struct Room { pub unread_notifications: Option, /// The tombstone state of this room. pub tombstone: Option, - /// The map of disambiguated display names for users who have the same display name - disambiguated_display_names: HashMap, } impl RoomName { @@ -302,7 +295,6 @@ impl Room { unread_highlight: None, unread_notifications: None, tombstone: None, - disambiguated_display_names: HashMap::new(), } } @@ -329,191 +321,231 @@ impl Room { /// Get the disambiguated display name for a member of this room. /// - /// If a member has no display name set, returns the MXID as a fallback. Additionally, we - /// return the MXID even if there is no such member in the room. + /// If a member has no display name set, returns the MXID as a fallback. + /// + /// This method never fails, even if user with the supplied MXID is not a member in the room. + /// In this case we still return the supplied MXID as a fallback. /// /// When displaying a room member's display name, clients *must* use this method to obtain the /// name instead of displaying the `RoomMember::display_name` directly. This is because /// multiple members can share the same display name in which case the display name has to be /// disambiguated. pub fn member_display_name<'a>(&'a self, id: &'a UserId) -> Cow<'a, str> { - let disambiguated_name = self - .disambiguated_display_names - .get(id) - .map(|s| s.as_str().into()); + let member = self.get_member(id); - if let Some(name) = disambiguated_name { - // The display name of the member is non-unique so we return a disambiguated version. - name - } else if let Some(member) = self - .joined_members - .get(id) - .or_else(|| self.invited_members.get(id)) - { - // The display name of the member is unique so we can return it directly if it is set. - // If not, we return his MXID. - member.name().into() - } else { - // There is no member with the requested MXID in the room. We still return the MXID. - id.as_ref().into() - } - } - - /// Process the member event of an entering user. - /// - /// Returns true if this made a change to the room's state, false otherwise. - fn add_member(&mut self, event: &MemberEvent) -> bool { - let new_member = RoomMember::new(event); - - match event.membership_change() { - MembershipChange::Joined => { - // Since the member is now joined, he shouldn't be tracked as an invited member any - // longer. - if self.invited_members.contains_key(&new_member.user_id) { - self.invited_members.remove(&new_member.user_id); + match member { + Some(member) => { + if member.display_name_ambiguous { + member.unique_name().into() + } else { + member.name().into() } - - self.joined_members - .insert(new_member.user_id.clone(), new_member.clone()) } - MembershipChange::Invited => self - .invited_members - .insert(new_member.user_id.clone(), new_member.clone()), - - _ => { - panic!("Room::add_member called on an event that is neither a join nor an invite.") - } - }; - - // Perform display name disambiguations, if necessary. - let disambiguations = self.disambiguation_updates(&new_member, MemberDirection::Entering); - for (id, name) in disambiguations.into_iter() { - match name { - None => self.disambiguated_display_names.remove(&id), - Some(name) => self.disambiguated_display_names.insert(id, name), - }; + // Even if there is no such member, we return the MXID that was given to us. + None => id.as_ref().into(), } - - true } - /// Process the member event of a leaving user. + /// Process the join or invite event for a new room member. /// - /// Returns true if this made a change to the room's state, false otherwise. - fn remove_member(&mut self, event: &MemberEvent) -> bool { - let leaving_member = RoomMember::new(event); + /// If the user is not already a member, he will be added. Otherwise, his state will be updated + /// to reflect the event's state. + /// + /// Returns a tuple of: + /// + /// 1. True if the event made changes to the room's state, false otherwise. + /// 2. Returns a map of display name disambiguations which tells us which members need to have + /// their display names disambiguated and to what. + /// + /// # Arguments + /// + /// * `target_member` - The ID of the member to add. + /// * `event` - The join or invite event for the specified room member. + fn add_member( + &mut self, + target_member: &UserId, + event: &MemberEvent, + ) -> (bool, HashMap) { + let new_member = RoomMember::new(event); // Perform display name disambiguations, if necessary. let disambiguations = - self.disambiguation_updates(&leaving_member, MemberDirection::Exiting); - for (id, name) in disambiguations.into_iter() { - match name { - None => self.disambiguated_display_names.remove(&id), - Some(name) => self.disambiguated_display_names.insert(id, name), - }; + self.ambiguity_updates(target_member, None, new_member.display_name.clone()); + + debug!("add_member: disambiguations: {:#?}", disambiguations); + + match event.content.membership { + MembershipState::Join => { + // Since the member is now joined, he shouldn't be tracked as an invited member any + // longer if he was previously tracked as such. + self.invited_members.remove(target_member); + + self.joined_members + .insert(target_member.clone(), new_member.clone()) + } + + MembershipState::Invite => self + .invited_members + .insert(target_member.clone(), new_member.clone()), + + _ => panic!("Room::add_member called on event that is neither `join` nor `invite`."), + }; + + for (id, is_ambiguous) in disambiguations.iter() { + self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; } - if self.joined_members.contains_key(&leaving_member.user_id) { - self.joined_members.remove(&leaving_member.user_id); - true - } else if self.invited_members.contains_key(&leaving_member.user_id) { - self.invited_members.remove(&leaving_member.user_id); - true - } else { - false + (true, disambiguations) + } + + /// Process the leaving event for a room member. + /// + /// Returns a tuple of: + /// + /// 1. True if the event made changes to the room's state, false otherwise. + /// 2. Returns a map of display name disambiguations which tells us which members need to have + /// their display names disambiguated and to what. + /// + /// # Arguments + /// + /// * `target_member` - The ID of the member to remove. + /// * `event` - The leaving event for the specified room member. + fn remove_member( + &mut self, + target_member: &UserId, + event: &MemberEvent, + ) -> (bool, HashMap) { + let leaving_member = RoomMember::new(event); + + if self.get_member(target_member).is_none() { + return (false, HashMap::new()); + } + + // Perform display name disambiguations, if necessary. + let disambiguations = + self.ambiguity_updates(target_member, leaving_member.display_name.clone(), None); + + debug!("remove_member: disambiguations: {:#?}", disambiguations); + + for (id, is_ambiguous) in disambiguations.iter() { + self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; + } + + // TODO: factor this out to a method called `remove_member` and rename this method + // to something like `process_member_leaving_event`. + self.joined_members + .remove(target_member) + .or_else(|| self.invited_members.remove(target_member)); + + (true, disambiguations) + } + + /// Check whether the user with the MXID `user_id` is joined or invited to the room. + /// + /// Returns true if so, false otherwise. + pub fn member_is_tracked(&self, user_id: &UserId) -> bool { + self.invited_members.contains_key(&user_id) || self.joined_members.contains_key(&user_id) + } + + /// Get a room member by user ID. + /// + /// If there is no such member, returns `None`. + pub fn get_member(&self, user_id: &UserId) -> Option<&RoomMember> { + self.joined_members + .get(user_id) + .or_else(|| self.invited_members.get(user_id)) + } + + /// Get a room member by user ID. + /// + /// If there is no such member, returns `None`. + pub fn get_member_mut(&mut self, user_id: &UserId) -> Option<&mut RoomMember> { + match self.joined_members.get_mut(user_id) { + None => self.invited_members.get_mut(user_id), + Some(m) => Some(m), } } - /// Given a room `member`, return the list of members which have the same display name. - /// - /// The `inclusive` parameter controls whether the passed member should be included in the - /// list or not. - fn shares_displayname_with(&self, member: &RoomMember, inclusive: bool) -> Vec { + /// Given a display name, return the set of members which share it. + fn display_name_equivalence_class(&self, name: &str) -> HashSet { let members = self .invited_members .iter() .chain(self.joined_members.iter()); - // Find all other users that share the same display name as the joining user. + // Find all other users that share the display name with the joining user. members - .filter(|(_, existing_member)| { + .filter(|(_, member)| { member .display_name .as_ref() - .and_then(|new_member_name| { - existing_member - .display_name - .as_ref() - .map(|existing_member_name| new_member_name == existing_member_name) - }) + .map(|other_name| other_name == name) .unwrap_or(false) }) - // If not an inclusive search, do not consider the member for which we are disambiguating. - .filter(|(id, _)| inclusive || **id != member.user_id) - .map(|(_, member)| member) - .cloned() + .map(|(_, member)| member.user_id.clone()) .collect() } - /// Given a room member, generate a map of all display name disambiguations which are necessary - /// in order to make that member's display name unique. + /// Given a change in a member's display name, determine the set of members whose display names + /// would either become newly ambiguous or no longer ambiguous after the change. /// - /// The `inclusive` parameter controls whether or not the member for which we are - /// disambiguating should be considered a current member of the room. + /// Returns a map of `user` -> `ambiguity` for affected room member. If `ambiguity` is `true`, + /// the member's display name is newly ambiguous after the change. If `false`, their display + /// name is no longer ambiguous (but it was before). /// - /// Returns a map from MXID to disambiguated name. - fn member_disambiguations( + /// The method *must* be called before any actual display name changes are performed in our + /// model (e.g. the `RoomMember` structs). + /// + /// # Arguments + /// + /// * `member` - The ID of the member changing their display name. + /// * `old_name` - Their old display name, prior to the change. May be `None` if they had no + /// display name in this room (e.g. because it was unset or because they were not a room + /// member). + /// * `new_name` - Their new display name, after the change. May be `None` if they are no + /// longer going to have a display name in this room after the change (e.g. because they are + /// unsetting it or leaving the room). + /// + /// Returns a map from user ID to the new ambiguity status. + fn ambiguity_updates( &self, - member: &RoomMember, - inclusive: bool, - ) -> HashMap { - let users_with_same_name = self.shares_displayname_with(member, inclusive); + member: &UserId, + old_name: Option, + new_name: Option, + ) -> HashMap { + // Must be called *before* any changes to the model. - let disambiguate_with = |members: Vec, f: fn(&RoomMember) -> String| { - members - .into_iter() - .map(|ref m| (m.user_id.clone(), f(m))) - .collect::>() + let old_name_eq_class = match old_name { + None => HashSet::new(), + Some(name) => self.display_name_equivalence_class(&name), }; - match users_with_same_name.len() { - 0 | 1 => HashMap::new(), - _ => disambiguate_with(users_with_same_name, |m: &RoomMember| m.unique_name()), - } - } + let disambiguate_old = match old_name_eq_class.len().saturating_sub(1) { + n if n > 1 => vec![(member.clone(), false)].into_iter().collect(), + 1 => old_name_eq_class.into_iter().map(|m| (m, false)).collect(), + 0 => HashMap::new(), + _ => panic!("impossible"), + }; - /// Calculate disambiguation updates needed when a room member either enters or exits. - fn disambiguation_updates( - &self, - member: &RoomMember, - when: MemberDirection, - ) -> HashMap> { - let before; - let after; + // - match when { - MemberDirection::Entering => { - before = self.member_disambiguations(member, false); - after = self.member_disambiguations(member, true); - } - MemberDirection::Exiting => { - before = self.member_disambiguations(member, true); - after = self.member_disambiguations(member, false); - } - } + let mut new_name_eq_class = match new_name { + None => HashSet::new(), + Some(name) => self.display_name_equivalence_class(&name), + }; - let mut res = before; - res.extend(after.clone()); + new_name_eq_class.insert(member.clone()); - res.into_iter() - .map(|(user_id, name)| { - if !after.contains_key(&user_id) { - (user_id, None) - } else { - (user_id, Some(name)) - } - }) + let disambiguate_new = match new_name_eq_class.len() { + 1 => HashMap::new(), + 2 => new_name_eq_class.into_iter().map(|m| (m, true)).collect(), + _ => vec![(member.clone(), true)].into_iter().collect(), + }; + + disambiguate_old + .into_iter() + .chain(disambiguate_new.into_iter()) .collect() } @@ -581,23 +613,69 @@ impl Room { /// Handle a room.member updating the room state if necessary. /// - /// Returns true if the joined member list changed, false otherwise. - pub fn handle_membership(&mut self, event: &MemberEvent) -> bool { + /// Returns a tuple of: + /// + /// 1. True if the joined member list changed, false otherwise. + /// 2. A map of display name disambiguations which tells us which members need to have their + /// display names disambiguated and to what. + pub fn handle_membership( + &mut self, + event: &MemberEvent, + state_event: bool, + ) -> (bool, HashMap) { use MembershipChange::*; + use MembershipState::*; - match event.membership_change() { - Invited | Joined => { - self.add_member(event) - } - Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { - self.remove_member(event) - } - ProfileChanged => { - self.update_member_profile(event) - } + trace!( + "Received {} event: {}", + if state_event { "state" } else { "timeline" }, + event.event_id + ); - // Not interested in other events. - _ => false, + let target_user = match UserId::try_from(event.state_key.clone()) { + Ok(id) => id, + Err(e) => { + error!("Received a member event with invalid state_key: {}", e); + return (false, HashMap::new()); + } + }; + + if state_event && !self.member_is_tracked(&target_user) { + debug!( + "handle_membership: User {user_id} is {state} the room {room_id} ({room_name})", + user_id = target_user, + state = event.content.membership.describe(), + room_id = self.room_id, + room_name = self.display_name(), + ); + + match event.content.membership { + Join | Invite => self.add_member(&target_user, event), + + // We are not interested in tracking past members for now + _ => (false, HashMap::new()), + } + } else { + let change = event.membership_change(); + + debug!( + "handle_membership: User {user_id} {action} the room {room_id} ({room_name})", + user_id = target_user, + action = change.describe(), + room_id = self.room_id, + room_name = self.display_name(), + ); + + match change { + Invited | Joined => self.add_member(&target_user, event), + Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { + self.remove_member(&target_user, event) + } + ProfileChanged => self.update_member_profile(&target_user, event), + + // Not interested in other events. + _ => (false, HashMap::new()), + } } } @@ -697,7 +775,7 @@ impl Room { pub fn receive_timeline_event(&mut self, event: &RoomEvent) -> bool { match event { // update to the current members of the room - RoomEvent::RoomMember(member) => self.handle_membership(member), + RoomEvent::RoomMember(member) => self.handle_membership(member, false).0, // finds all events related to the name of the room for later use RoomEvent::RoomName(name) => self.handle_room_name(name), RoomEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -722,7 +800,7 @@ impl Room { pub fn receive_state_event(&mut self, event: &StateEvent) -> bool { match event { // update to the current members of the room - StateEvent::RoomMember(member) => self.handle_membership(member), + StateEvent::RoomMember(member) => self.handle_membership(member, true).0, // finds all events related to the name of the room for later use StateEvent::RoomName(name) => self.handle_room_name(name), StateEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -777,8 +855,8 @@ impl Room { && member.presence.as_ref() == Some(presence) && member.status_msg == *status_msg && member.last_active_ago == *last_active_ago - && member.currently_active == *currently_active { - + && member.currently_active == *currently_active + { // Everything is the same, nothing to do. false } else { @@ -799,33 +877,70 @@ impl Room { // we don't know about. false } - } /// Process an update of a member's profile. /// + /// Returns a tuple of: + /// + /// 1. True if the event made changes to the room's state, false otherwise. + /// 2. A map of display name disambiguations which tells us which members need to have their + /// display names disambiguated and to what. + /// /// # Arguments /// - /// * `event` - The profile update event for a specified room member. - // TODO: NEXT: Add disambiguation handling here - pub(crate) fn update_member_profile(&mut self, event: &MemberEvent) -> bool { - let user_id = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { - id - } else { - return false; + /// * `target_member` - The ID of the member to update. + /// * `event` - The profile update event for the specified room member. + pub fn update_member_profile( + &mut self, + target_member: &UserId, + event: &MemberEvent, + ) -> (bool, HashMap) { + let old_name = self + .get_member(target_member) + .map_or_else(|| None, |m| m.display_name.clone()); + let new_name = event.content.displayname.clone(); + + debug!( + "update_member_profile [{}]: from nick {:#?} to nick {:#?}", + self.room_id, old_name, &new_name + ); + + let disambiguations = + self.ambiguity_updates(target_member, old_name.clone(), new_name.clone()); + for (id, is_ambiguous) in disambiguations.iter() { + if self.get_member_mut(id).is_none() { + error!("update_member_profile: I'm about to fail for id {}. user_id = {}\nevent = {:#?}", + id, + target_member, + event); + } else { + self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; + } + } + + debug!( + "update_member_profile [{}]: disambiguations: {:#?}", + self.room_id, &disambiguations + ); + + let changed = match self.get_member_mut(target_member) { + Some(member) => { + member.display_name = new_name; + member.avatar_url = event.content.avatar_url.clone(); + true + } + None => { + error!( + "update_member_profile [{}]: user {} does not exist", + self.room_id, target_member + ); + + false + } }; - if let Some(member) = self.joined_members.get_mut(&user_id) { - member.display_name = event.content.displayname.clone(); - member.avatar_url = event.content.avatar_url.clone(); - true - } else if let Some(member) = self.invited_members.get_mut(&user_id) { - member.display_name = event.content.displayname.clone(); - member.avatar_url = event.content.avatar_url.clone(); - true - } else { - false - } + (changed, disambiguations) } /// Process an update of a member's power level. @@ -858,6 +973,48 @@ impl Room { } } +trait Describe { + fn describe(&self) -> String; +} + +impl Describe for MembershipState { + fn describe(&self) -> String { + use MembershipState::*; + + match self { + Ban => "is banned in", + Invite => "is invited to", + Join => "is a member of", + Knock => "is requesting access", + Leave => "left", + } + .to_string() + } +} + +impl Describe for MembershipChange { + fn describe(&self) -> String { + use MembershipChange::*; + + match self { + Invited => "got invited to", + Joined => "joined", + Kicked => "got kicked from", + Banned => "got banned from", + Unbanned => "got unbanned from", + KickedAndBanned => "got kicked and banned from", + InvitationRejected => "rejected the invitation to", + InvitationRevoked => "got their invitation revoked from", + Left => "left", + ProfileChanged => "changed their profile", + None => "did nothing in", + NotImplemented => "NOT IMPLEMENTED", + Error => "ERROR", + } + .to_string() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index ced14760..98fc4fd7 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -33,6 +33,8 @@ pub struct RoomMember { pub user_id: UserId, /// The human readable name of the user. pub display_name: Option, + /// Whether the member's display name is ambiguous due to being shared with other members. + pub display_name_ambiguous: bool, /// The matrix url of the users avatar. pub avatar_url: Option, /// The time, in ms, since the user interacted with the server. @@ -76,6 +78,7 @@ impl PartialEq for RoomMember { && self.user_id == other.user_id && self.name == other.name && self.display_name == other.display_name + && self.display_name_ambiguous == other.display_name_ambiguous && self.avatar_url == other.avatar_url && self.last_active_ago == other.last_active_ago } @@ -88,6 +91,7 @@ impl RoomMember { room_id: event.room_id.as_ref().map(|id| id.to_string()), user_id: UserId::try_from(event.state_key.as_str()).unwrap(), display_name: event.content.displayname.clone(), + display_name_ambiguous: false, avatar_url: event.content.avatar_url.clone(), presence: None, status_msg: None, diff --git a/matrix_sdk_base/src/state/mod.rs b/matrix_sdk_base/src/state/mod.rs index 6bd18820..dd901082 100644 --- a/matrix_sdk_base/src/state/mod.rs +++ b/matrix_sdk_base/src/state/mod.rs @@ -159,7 +159,6 @@ mod test { "creator": null, "joined_members": {}, "invited_members": {}, - "disambiguated_display_names": {}, "typing_users": [], "power_levels": null, "encrypted": null, @@ -176,7 +175,6 @@ mod test { serde_json::json!({ "!roomid:example.com": { "room_id": "!roomid:example.com", - "disambiguated_display_names": {}, "room_name": { "name": null, "canonical_alias": null, From 949305da7254315bba030199356a03df2911d776 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Mon, 6 Jul 2020 15:22:49 +0200 Subject: [PATCH 21/83] Clarify comment. --- matrix_sdk_base/src/models/room.rs | 302 +++++++++-------------------- 1 file changed, 91 insertions(+), 211 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 61c23a98..0d27cf81 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -18,7 +18,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use serde::{Deserialize, Serialize}; -use tracing::{debug, error, trace}; +use tracing::{debug, instrument}; #[cfg(feature = "messages")] use super::message::MessageQueue; @@ -31,7 +31,7 @@ use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, - member::{MemberEvent, MembershipChange, MembershipState}, + member::{MemberEvent, MembershipChange}, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, @@ -321,10 +321,8 @@ impl Room { /// Get the disambiguated display name for a member of this room. /// - /// If a member has no display name set, returns the MXID as a fallback. - /// - /// This method never fails, even if user with the supplied MXID is not a member in the room. - /// In this case we still return the supplied MXID as a fallback. + /// If a member has no display name set, returns the MXID as a fallback. Additionally, we + /// return the MXID even if there is no such member in the room. /// /// When displaying a room member's display name, clients *must* use this method to obtain the /// name instead of displaying the `RoomMember::display_name` directly. This is because @@ -340,56 +338,49 @@ impl Room { } else { member.name().into() } - } + }, // Even if there is no such member, we return the MXID that was given to us. None => id.as_ref().into(), } } - /// Process the join or invite event for a new room member. - /// - /// If the user is not already a member, he will be added. Otherwise, his state will be updated - /// to reflect the event's state. + /// Process the member event of an entering user. /// /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. /// 2. Returns a map of display name disambiguations which tells us which members need to have /// their display names disambiguated and to what. - /// - /// # Arguments - /// - /// * `target_member` - The ID of the member to add. - /// * `event` - The join or invite event for the specified room member. - fn add_member( - &mut self, - target_member: &UserId, - event: &MemberEvent, - ) -> (bool, HashMap) { + #[instrument] + fn add_member(&mut self, event: &MemberEvent) -> (bool, HashMap) { let new_member = RoomMember::new(event); // Perform display name disambiguations, if necessary. - let disambiguations = - self.ambiguity_updates(target_member, None, new_member.display_name.clone()); + let disambiguations = self.disambiguation_updates(&new_member.user_id, None, new_member.display_name.clone()); - debug!("add_member: disambiguations: {:#?}", disambiguations); + debug!( + "add_member: disambiguations: {:#?}", + disambiguations + ); - match event.content.membership { - MembershipState::Join => { + match event.membership_change() { + MembershipChange::Joined => { // Since the member is now joined, he shouldn't be tracked as an invited member any // longer if he was previously tracked as such. - self.invited_members.remove(target_member); + self.invited_members.remove(&new_member.user_id); self.joined_members - .insert(target_member.clone(), new_member.clone()) + .insert(new_member.user_id.clone(), new_member.clone()) } - MembershipState::Invite => self + MembershipChange::Invited => self .invited_members - .insert(target_member.clone(), new_member.clone()), + .insert(new_member.user_id.clone(), new_member.clone()), - _ => panic!("Room::add_member called on event that is neither `join` nor `invite`."), + _ => { + panic!("Room::add_member called on an event that is neither a join nor an invite.") + } }; for (id, is_ambiguous) in disambiguations.iter() { @@ -399,34 +390,29 @@ impl Room { (true, disambiguations) } - /// Process the leaving event for a room member. + /// Process the member event of a leaving user. /// /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. /// 2. Returns a map of display name disambiguations which tells us which members need to have /// their display names disambiguated and to what. - /// - /// # Arguments - /// - /// * `target_member` - The ID of the member to remove. - /// * `event` - The leaving event for the specified room member. - fn remove_member( - &mut self, - target_member: &UserId, - event: &MemberEvent, - ) -> (bool, HashMap) { + #[instrument] + fn remove_member(&mut self, event: &MemberEvent) -> (bool, HashMap) { let leaving_member = RoomMember::new(event); - if self.get_member(target_member).is_none() { + if self.get_member(&leaving_member.user_id).is_none() { return (false, HashMap::new()); } // Perform display name disambiguations, if necessary. let disambiguations = - self.ambiguity_updates(target_member, leaving_member.display_name.clone(), None); + self.disambiguation_updates(&leaving_member.user_id, leaving_member.display_name.clone(), None); - debug!("remove_member: disambiguations: {:#?}", disambiguations); + debug!( + "remove_member: disambiguations: {:#?}", + disambiguations + ); for (id, is_ambiguous) in disambiguations.iter() { self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; @@ -434,20 +420,12 @@ impl Room { // TODO: factor this out to a method called `remove_member` and rename this method // to something like `process_member_leaving_event`. - self.joined_members - .remove(target_member) - .or_else(|| self.invited_members.remove(target_member)); + self.joined_members.remove(&leaving_member.user_id).or_else(|| + self.invited_members.remove(&leaving_member.user_id)); (true, disambiguations) } - /// Check whether the user with the MXID `user_id` is joined or invited to the room. - /// - /// Returns true if so, false otherwise. - pub fn member_is_tracked(&self, user_id: &UserId) -> bool { - self.invited_members.contains_key(&user_id) || self.joined_members.contains_key(&user_id) - } - /// Get a room member by user ID. /// /// If there is no such member, returns `None`. @@ -463,7 +441,7 @@ impl Room { pub fn get_member_mut(&mut self, user_id: &UserId) -> Option<&mut RoomMember> { match self.joined_members.get_mut(user_id) { None => self.invited_members.get_mut(user_id), - Some(m) => Some(m), + Some(m) => Some(m) } } @@ -476,39 +454,19 @@ impl Room { // Find all other users that share the display name with the joining user. members - .filter(|(_, member)| { - member - .display_name - .as_ref() - .map(|other_name| other_name == name) - .unwrap_or(false) - }) + .filter(|(_, member)| member.display_name.as_ref().map(|other_name| other_name == name).unwrap_or(false)) .map(|(_, member)| member.user_id.clone()) .collect() } - /// Given a change in a member's display name, determine the set of members whose display names - /// would either become newly ambiguous or no longer ambiguous after the change. + /// Given a room member, generate a map of all display name disambiguations which are necessary + /// in order to make that member's display name unique. /// - /// Returns a map of `user` -> `ambiguity` for affected room member. If `ambiguity` is `true`, - /// the member's display name is newly ambiguous after the change. If `false`, their display - /// name is no longer ambiguous (but it was before). + /// The `inclusive` parameter controls whether or not the member for which we are + /// disambiguating should be considered a current member of the room. /// - /// The method *must* be called before any actual display name changes are performed in our - /// model (e.g. the `RoomMember` structs). - /// - /// # Arguments - /// - /// * `member` - The ID of the member changing their display name. - /// * `old_name` - Their old display name, prior to the change. May be `None` if they had no - /// display name in this room (e.g. because it was unset or because they were not a room - /// member). - /// * `new_name` - Their new display name, after the change. May be `None` if they are no - /// longer going to have a display name in this room after the change (e.g. because they are - /// unsetting it or leaving the room). - /// - /// Returns a map from user ID to the new ambiguity status. - fn ambiguity_updates( + /// Returns a map from MXID to disambiguated name. + fn disambiguation_updates( &self, member: &UserId, old_name: Option, @@ -525,7 +483,7 @@ impl Room { n if n > 1 => vec![(member.clone(), false)].into_iter().collect(), 1 => old_name_eq_class.into_iter().map(|m| (m, false)).collect(), 0 => HashMap::new(), - _ => panic!("impossible"), + _ => panic!("impossible") }; // @@ -543,10 +501,7 @@ impl Room { _ => vec![(member.clone(), true)].into_iter().collect(), }; - disambiguate_old - .into_iter() - .chain(disambiguate_new.into_iter()) - .collect() + disambiguate_old.into_iter().chain(disambiguate_new.into_iter()).collect() } /// Add to the list of `RoomAliasId`s. @@ -621,61 +576,45 @@ impl Room { pub fn handle_membership( &mut self, event: &MemberEvent, - state_event: bool, ) -> (bool, HashMap) { use MembershipChange::*; - use MembershipState::*; - trace!( - "Received {} event: {}", - if state_event { "state" } else { "timeline" }, - event.event_id - ); + match event.membership_change() { + Invited | Joined => { + debug!( + "handle_membership: User {} entering room {} ({}) (joined or invited)", + event.state_key, + self.room_id, + self.display_name(), + ); - let target_user = match UserId::try_from(event.state_key.clone()) { - Ok(id) => id, - Err(e) => { - error!("Received a member event with invalid state_key: {}", e); - return (false, HashMap::new()); + self.add_member(event) } - }; + Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { + debug!( + "handle_membership: User {} exiting room {} ({}) (leaving, kicked, banned or invitation rejected)", + event.state_key, + self.room_id, + self.display_name(), + ); - if state_event && !self.member_is_tracked(&target_user) { - debug!( - "handle_membership: User {user_id} is {state} the room {room_id} ({room_name})", - user_id = target_user, - state = event.content.membership.describe(), - room_id = self.room_id, - room_name = self.display_name(), - ); - - match event.content.membership { - Join | Invite => self.add_member(&target_user, event), - - // We are not interested in tracking past members for now - _ => (false, HashMap::new()), + self.remove_member(event) } - } else { - let change = event.membership_change(); + ProfileChanged => { + debug!( + "handle_membership: Profile changed for user {} in room {} ({}) (displayname={:?}, avatar={:?}", + event.state_key, + self.room_id, + self.display_name(), + event.content.displayname, + event.content.avatar_url + ); - debug!( - "handle_membership: User {user_id} {action} the room {room_id} ({room_name})", - user_id = target_user, - action = change.describe(), - room_id = self.room_id, - room_name = self.display_name(), - ); - - match change { - Invited | Joined => self.add_member(&target_user, event), - Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { - self.remove_member(&target_user, event) - } - ProfileChanged => self.update_member_profile(&target_user, event), - - // Not interested in other events. - _ => (false, HashMap::new()), + self.update_member_profile(event) } + + // Not interested in other events. + _ => (false, HashMap::new()), } } @@ -775,7 +714,7 @@ impl Room { pub fn receive_timeline_event(&mut self, event: &RoomEvent) -> bool { match event { // update to the current members of the room - RoomEvent::RoomMember(member) => self.handle_membership(member, false).0, + RoomEvent::RoomMember(member) => self.handle_membership(member).0, // finds all events related to the name of the room for later use RoomEvent::RoomName(name) => self.handle_room_name(name), RoomEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -800,7 +739,7 @@ impl Room { pub fn receive_state_event(&mut self, event: &StateEvent) -> bool { match event { // update to the current members of the room - StateEvent::RoomMember(member) => self.handle_membership(member, true).0, + StateEvent::RoomMember(member) => self.handle_membership(member).0, // finds all events related to the name of the room for later use StateEvent::RoomName(name) => self.handle_room_name(name), StateEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -889,55 +828,38 @@ impl Room { /// /// # Arguments /// - /// * `target_member` - The ID of the member to update. - /// * `event` - The profile update event for the specified room member. + /// * `event` - The profile update event for a specified room member. + #[instrument] pub fn update_member_profile( &mut self, - target_member: &UserId, event: &MemberEvent, ) -> (bool, HashMap) { - let old_name = self - .get_member(target_member) - .map_or_else(|| None, |m| m.display_name.clone()); + let user_id = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { + id + } else { + return (false, HashMap::new()); + }; + + let old_name = self.get_member(&user_id).map_or_else(|| None, |m| m.display_name.clone()); let new_name = event.content.displayname.clone(); - debug!( - "update_member_profile [{}]: from nick {:#?} to nick {:#?}", - self.room_id, old_name, &new_name - ); - - let disambiguations = - self.ambiguity_updates(target_member, old_name.clone(), new_name.clone()); + let disambiguations = self.disambiguation_updates(&user_id, old_name, new_name); for (id, is_ambiguous) in disambiguations.iter() { - if self.get_member_mut(id).is_none() { - error!("update_member_profile: I'm about to fail for id {}. user_id = {}\nevent = {:#?}", - id, - target_member, - event); - } else { - self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; - } + self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; } debug!( - "update_member_profile [{}]: disambiguations: {:#?}", - self.room_id, &disambiguations + "update_member_profile: disambiguations: {:#?}", + &disambiguations ); - let changed = match self.get_member_mut(target_member) { + let changed = match self.get_member_mut(&user_id) { Some(member) => { - member.display_name = new_name; + member.display_name = event.content.displayname.clone(); member.avatar_url = event.content.avatar_url.clone(); true } - None => { - error!( - "update_member_profile [{}]: user {} does not exist", - self.room_id, target_member - ); - - false - } + None => false, }; (changed, disambiguations) @@ -973,48 +895,6 @@ impl Room { } } -trait Describe { - fn describe(&self) -> String; -} - -impl Describe for MembershipState { - fn describe(&self) -> String { - use MembershipState::*; - - match self { - Ban => "is banned in", - Invite => "is invited to", - Join => "is a member of", - Knock => "is requesting access", - Leave => "left", - } - .to_string() - } -} - -impl Describe for MembershipChange { - fn describe(&self) -> String { - use MembershipChange::*; - - match self { - Invited => "got invited to", - Joined => "joined", - Kicked => "got kicked from", - Banned => "got banned from", - Unbanned => "got unbanned from", - KickedAndBanned => "got kicked and banned from", - InvitationRejected => "rejected the invitation to", - InvitationRevoked => "got their invitation revoked from", - Left => "left", - ProfileChanged => "changed their profile", - None => "did nothing in", - NotImplemented => "NOT IMPLEMENTED", - Error => "ERROR", - } - .to_string() - } -} - #[cfg(test)] mod test { use super::*; From ec81a5e539913c34fef2768d33a649311954974c Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 9 Jul 2020 10:09:57 +0200 Subject: [PATCH 22/83] Implement Room::member_is_tracked. --- matrix_sdk_base/src/models/room.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 0d27cf81..648a00cf 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -426,6 +426,13 @@ impl Room { (true, disambiguations) } + /// Check whether the user with the MXID `user_id` is joined or invited to the room. + /// + /// Returns true if so, false otherwise. + pub fn member_is_tracked(&self, user_id: &UserId) -> bool { + self.invited_members.contains_key(&user_id) || self.joined_members.contains_key(&user_id) + } + /// Get a room member by user ID. /// /// If there is no such member, returns `None`. From b16724841d02c6d32775f1c9f46e19d1e92ba4ea Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 9 Jul 2020 12:22:06 +0200 Subject: [PATCH 23/83] Correct state tracking of room members. - use `state_key` instead of `user_id` to determine which member is affected by the event - assign state directly from the event in `add_member` instead of using `membership_change` - expand/fix docstrings - add some logging --- matrix_sdk_base/src/client.rs | 4 +- matrix_sdk_base/src/models/room.rs | 208 +++++++++++++++++++---------- 2 files changed, 143 insertions(+), 69 deletions(-) diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 2e0bad13..613ad219 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -734,7 +734,7 @@ impl BaseClient { let mut room = room_lock.write().await; if let RoomEvent::RoomMember(mem_event) = &mut e { - let (changed, _) = room.handle_membership(mem_event); + let (changed, _) = room.handle_membership(mem_event, false); // The memberlist of the room changed, invalidate the group session // of the room. @@ -771,7 +771,7 @@ impl BaseClient { let mut room = room_lock.write().await; if let StateEvent::RoomMember(e) = event { - let (changed, _) = room.handle_membership(e); + let (changed, _) = room.handle_membership(e, true); // The memberlist of the room changed, invalidate the group session // of the room. diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 648a00cf..41379b71 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -18,7 +18,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use serde::{Deserialize, Serialize}; -use tracing::{debug, instrument}; +use tracing::{trace, debug, error}; #[cfg(feature = "messages")] use super::message::MessageQueue; @@ -31,7 +31,7 @@ use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, - member::{MemberEvent, MembershipChange}, + member::{MemberEvent, MembershipState, MembershipChange}, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, @@ -345,41 +345,47 @@ impl Room { } } - /// Process the member event of an entering user. + /// Process the join or invite event for a new room member. + /// + /// This method should be called only on events which add new members, not on existing ones. /// /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. /// 2. Returns a map of display name disambiguations which tells us which members need to have /// their display names disambiguated and to what. - #[instrument] - fn add_member(&mut self, event: &MemberEvent) -> (bool, HashMap) { + /// + /// # Arguments + /// + /// * `target_member` - The ID of the member to add. + /// * `event` - The join or invite event for the specified room member. + fn add_member(&mut self, target_member: &UserId, event: &MemberEvent) -> (bool, HashMap) { let new_member = RoomMember::new(event); // Perform display name disambiguations, if necessary. - let disambiguations = self.disambiguation_updates(&new_member.user_id, None, new_member.display_name.clone()); + let disambiguations = self.disambiguation_updates(target_member, None, new_member.display_name.clone()); debug!( "add_member: disambiguations: {:#?}", disambiguations ); - match event.membership_change() { - MembershipChange::Joined => { + match event.content.membership { + MembershipState::Join => { // Since the member is now joined, he shouldn't be tracked as an invited member any // longer if he was previously tracked as such. - self.invited_members.remove(&new_member.user_id); + self.invited_members.remove(target_member); self.joined_members - .insert(new_member.user_id.clone(), new_member.clone()) + .insert(target_member.clone(), new_member.clone()) } - MembershipChange::Invited => self + MembershipState::Invite => self .invited_members - .insert(new_member.user_id.clone(), new_member.clone()), + .insert(target_member.clone(), new_member.clone()), _ => { - panic!("Room::add_member called on an event that is neither a join nor an invite.") + panic!("Room::add_member called on event that is neither `join` nor `invite`.") } }; @@ -390,24 +396,28 @@ impl Room { (true, disambiguations) } - /// Process the member event of a leaving user. + /// Process the leaving event for a room member. /// /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. /// 2. Returns a map of display name disambiguations which tells us which members need to have /// their display names disambiguated and to what. - #[instrument] - fn remove_member(&mut self, event: &MemberEvent) -> (bool, HashMap) { + /// + /// # Arguments + /// + /// * `target_member` - The ID of the member to remove. + /// * `event` - The leaving event for the specified room member. + fn remove_member(&mut self, target_member: &UserId, event: &MemberEvent) -> (bool, HashMap) { let leaving_member = RoomMember::new(event); - if self.get_member(&leaving_member.user_id).is_none() { + if self.get_member(target_member).is_none() { return (false, HashMap::new()); } // Perform display name disambiguations, if necessary. let disambiguations = - self.disambiguation_updates(&leaving_member.user_id, leaving_member.display_name.clone(), None); + self.disambiguation_updates(target_member, leaving_member.display_name.clone(), None); debug!( "remove_member: disambiguations: {:#?}", @@ -420,8 +430,7 @@ impl Room { // TODO: factor this out to a method called `remove_member` and rename this method // to something like `process_member_leaving_event`. - self.joined_members.remove(&leaving_member.user_id).or_else(|| - self.invited_members.remove(&leaving_member.user_id)); + self.joined_members.remove(target_member).or_else(|| self.invited_members.remove(target_member)); (true, disambiguations) } @@ -583,45 +592,95 @@ impl Room { pub fn handle_membership( &mut self, event: &MemberEvent, + state_event: bool ) -> (bool, HashMap) { + use MembershipState::*; use MembershipChange::*; - match event.membership_change() { - Invited | Joined => { - debug!( - "handle_membership: User {} entering room {} ({}) (joined or invited)", - event.state_key, - self.room_id, - self.display_name(), - ); + trace!("Received {} event: {}", if state_event { "state" } else { "timeline" }, + event.event_id); - self.add_member(event) + let target_user = match UserId::try_from(event.state_key.clone()) { + Ok(id) => id, + Err(e) => { + error!("Received a member event with invalid state_key: {}", e); + return (false, HashMap::new()); } - Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { - debug!( - "handle_membership: User {} exiting room {} ({}) (leaving, kicked, banned or invitation rejected)", - event.state_key, - self.room_id, - self.display_name(), - ); + }; - self.remove_member(event) + if state_event && !self.member_is_tracked(&target_user) { + match event.content.membership { + Join => { + debug!("handle_membership: User {} is now a member of the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), + ); + + self.add_member(&target_user, event) + } + + Invite => { + debug!("handle_membership: User {} is now invited to the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), + ); + + self.add_member(&target_user, event) + } + + // We are not interested in tracking past members for now + _ => (false, HashMap::new()), } - ProfileChanged => { - debug!( - "handle_membership: Profile changed for user {} in room {} ({}) (displayname={:?}, avatar={:?}", - event.state_key, - self.room_id, - self.display_name(), - event.content.displayname, - event.content.avatar_url - ); + } else { + match event.membership_change() { + Invited => { + debug!( + "handle_membership: User {} got invited to the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), + ); - self.update_member_profile(event) + self.add_member(&target_user, event) + } + Joined => { + debug!( + "handle_membership: User {} joins the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), + ); + + self.add_member(&target_user, event) + } + Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { + debug!( + "handle_membership: User {} exiting room {} ({}) (leaving, kicked, banned or invitation rejected)", + event.state_key, + self.room_id, + self.display_name(), + ); + + self.remove_member(&target_user, event) + } + ProfileChanged => { + debug!( + "handle_membership: Profile changed for user {} in room {} ({}) (displayname={:?}, avatar={:?}", + event.state_key, + self.room_id, + self.display_name(), + event.content.displayname, + event.content.avatar_url + ); + + self.update_member_profile(&target_user, event) + } + + // Not interested in other events. + _ => (false, HashMap::new()), } - - // Not interested in other events. - _ => (false, HashMap::new()), } } @@ -721,7 +780,7 @@ impl Room { pub fn receive_timeline_event(&mut self, event: &RoomEvent) -> bool { match event { // update to the current members of the room - RoomEvent::RoomMember(member) => self.handle_membership(member).0, + RoomEvent::RoomMember(member) => self.handle_membership(member, false).0, // finds all events related to the name of the room for later use RoomEvent::RoomName(name) => self.handle_room_name(name), RoomEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -746,7 +805,7 @@ impl Room { pub fn receive_state_event(&mut self, event: &StateEvent) -> bool { match event { // update to the current members of the room - StateEvent::RoomMember(member) => self.handle_membership(member).0, + StateEvent::RoomMember(member) => self.handle_membership(member, true).0, // finds all events related to the name of the room for later use StateEvent::RoomName(name) => self.handle_room_name(name), StateEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), @@ -835,38 +894,53 @@ impl Room { /// /// # Arguments /// - /// * `event` - The profile update event for a specified room member. - #[instrument] + /// * `target_member` - The ID of the member to update. + /// * `event` - The profile update event for the specified room member. pub fn update_member_profile( &mut self, + target_member: &UserId, event: &MemberEvent, ) -> (bool, HashMap) { - let user_id = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { - id - } else { - return (false, HashMap::new()); - }; - - let old_name = self.get_member(&user_id).map_or_else(|| None, |m| m.display_name.clone()); + let old_name = self.get_member(target_member).map_or_else(|| None, |m| m.display_name.clone()); let new_name = event.content.displayname.clone(); - let disambiguations = self.disambiguation_updates(&user_id, old_name, new_name); + debug!("update_member_profile [{}]: from nick {:#?} to nick {:#?}", + self.room_id, + old_name, &new_name); + + let disambiguations = self.disambiguation_updates(target_member, old_name.clone(), new_name.clone()); for (id, is_ambiguous) in disambiguations.iter() { - self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; + if self.get_member_mut(id).is_none() { + error!("update_member_profile: I'm about to fail for id {}. user_id = {}\nevent = {:#?}", + id, + target_member, + event); + } else { + self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; + } } debug!( - "update_member_profile: disambiguations: {:#?}", + "update_member_profile [{}]: disambiguations: {:#?}", + self.room_id, &disambiguations ); - let changed = match self.get_member_mut(&user_id) { + let changed = match self.get_member_mut(target_member) { Some(member) => { - member.display_name = event.content.displayname.clone(); + member.display_name = new_name; member.avatar_url = event.content.avatar_url.clone(); true } - None => false, + None => { + error!( + "update_member_profile [{}]: user {} does not exist", + self.room_id, + target_member + ); + + false + }, }; (changed, disambiguations) From 390a1aa12c7b461a2a7869e53e8876da7fa19d4d Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 9 Jul 2020 12:26:04 +0200 Subject: [PATCH 24/83] Clarify member_display_name docstring. --- matrix_sdk_base/src/models/room.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 41379b71..a96f5c84 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -321,8 +321,10 @@ impl Room { /// Get the disambiguated display name for a member of this room. /// - /// If a member has no display name set, returns the MXID as a fallback. Additionally, we - /// return the MXID even if there is no such member in the room. + /// If a member has no display name set, returns the MXID as a fallback. + /// + /// This method never fails, even if user with the supplied MXID is not a member in the room. + /// In this case we still return the supplied MXID as a fallback. /// /// When displaying a room member's display name, clients *must* use this method to obtain the /// name instead of displaying the `RoomMember::display_name` directly. This is because From a8f24da3ba9f6b09757aa18778eed23a3e986447 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 9 Jul 2020 14:47:57 +0200 Subject: [PATCH 25/83] cargo fmt --- matrix_sdk_base/src/models/room.rs | 107 +++++++++++++--------- matrix_sdk_base/src/models/room_member.rs | 10 +- 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index a96f5c84..9736ef0b 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -18,7 +18,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; use serde::{Deserialize, Serialize}; -use tracing::{trace, debug, error}; +use tracing::{debug, error, trace}; #[cfg(feature = "messages")] use super::message::MessageQueue; @@ -31,7 +31,7 @@ use crate::events::room::{ aliases::AliasesEvent, canonical_alias::CanonicalAliasEvent, encryption::EncryptionEvent, - member::{MemberEvent, MembershipState, MembershipChange}, + member::{MemberEvent, MembershipChange, MembershipState}, name::NameEvent, power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, tombstone::TombstoneEvent, @@ -340,7 +340,7 @@ impl Room { } else { member.name().into() } - }, + } // Even if there is no such member, we return the MXID that was given to us. None => id.as_ref().into(), @@ -361,16 +361,18 @@ impl Room { /// /// * `target_member` - The ID of the member to add. /// * `event` - The join or invite event for the specified room member. - fn add_member(&mut self, target_member: &UserId, event: &MemberEvent) -> (bool, HashMap) { + fn add_member( + &mut self, + target_member: &UserId, + event: &MemberEvent, + ) -> (bool, HashMap) { let new_member = RoomMember::new(event); // Perform display name disambiguations, if necessary. - let disambiguations = self.disambiguation_updates(target_member, None, new_member.display_name.clone()); + let disambiguations = + self.disambiguation_updates(target_member, None, new_member.display_name.clone()); - debug!( - "add_member: disambiguations: {:#?}", - disambiguations - ); + debug!("add_member: disambiguations: {:#?}", disambiguations); match event.content.membership { MembershipState::Join => { @@ -386,9 +388,7 @@ impl Room { .invited_members .insert(target_member.clone(), new_member.clone()), - _ => { - panic!("Room::add_member called on event that is neither `join` nor `invite`.") - } + _ => panic!("Room::add_member called on event that is neither `join` nor `invite`."), }; for (id, is_ambiguous) in disambiguations.iter() { @@ -410,7 +410,11 @@ impl Room { /// /// * `target_member` - The ID of the member to remove. /// * `event` - The leaving event for the specified room member. - fn remove_member(&mut self, target_member: &UserId, event: &MemberEvent) -> (bool, HashMap) { + fn remove_member( + &mut self, + target_member: &UserId, + event: &MemberEvent, + ) -> (bool, HashMap) { let leaving_member = RoomMember::new(event); if self.get_member(target_member).is_none() { @@ -421,10 +425,7 @@ impl Room { let disambiguations = self.disambiguation_updates(target_member, leaving_member.display_name.clone(), None); - debug!( - "remove_member: disambiguations: {:#?}", - disambiguations - ); + debug!("remove_member: disambiguations: {:#?}", disambiguations); for (id, is_ambiguous) in disambiguations.iter() { self.get_member_mut(id).unwrap().display_name_ambiguous = *is_ambiguous; @@ -432,7 +433,9 @@ impl Room { // TODO: factor this out to a method called `remove_member` and rename this method // to something like `process_member_leaving_event`. - self.joined_members.remove(target_member).or_else(|| self.invited_members.remove(target_member)); + self.joined_members + .remove(target_member) + .or_else(|| self.invited_members.remove(target_member)); (true, disambiguations) } @@ -459,7 +462,7 @@ impl Room { pub fn get_member_mut(&mut self, user_id: &UserId) -> Option<&mut RoomMember> { match self.joined_members.get_mut(user_id) { None => self.invited_members.get_mut(user_id), - Some(m) => Some(m) + Some(m) => Some(m), } } @@ -472,7 +475,13 @@ impl Room { // Find all other users that share the display name with the joining user. members - .filter(|(_, member)| member.display_name.as_ref().map(|other_name| other_name == name).unwrap_or(false)) + .filter(|(_, member)| { + member + .display_name + .as_ref() + .map(|other_name| other_name == name) + .unwrap_or(false) + }) .map(|(_, member)| member.user_id.clone()) .collect() } @@ -501,7 +510,7 @@ impl Room { n if n > 1 => vec![(member.clone(), false)].into_iter().collect(), 1 => old_name_eq_class.into_iter().map(|m| (m, false)).collect(), 0 => HashMap::new(), - _ => panic!("impossible") + _ => panic!("impossible"), }; // @@ -519,7 +528,10 @@ impl Room { _ => vec![(member.clone(), true)].into_iter().collect(), }; - disambiguate_old.into_iter().chain(disambiguate_new.into_iter()).collect() + disambiguate_old + .into_iter() + .chain(disambiguate_new.into_iter()) + .collect() } /// Add to the list of `RoomAliasId`s. @@ -594,13 +606,16 @@ impl Room { pub fn handle_membership( &mut self, event: &MemberEvent, - state_event: bool + state_event: bool, ) -> (bool, HashMap) { - use MembershipState::*; use MembershipChange::*; + use MembershipState::*; - trace!("Received {} event: {}", if state_event { "state" } else { "timeline" }, - event.event_id); + trace!( + "Received {} event: {}", + if state_event { "state" } else { "timeline" }, + event.event_id + ); let target_user = match UserId::try_from(event.state_key.clone()) { Ok(id) => id, @@ -613,20 +628,22 @@ impl Room { if state_event && !self.member_is_tracked(&target_user) { match event.content.membership { Join => { - debug!("handle_membership: User {} is now a member of the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), + debug!( + "handle_membership: User {} is now a member of the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), ); self.add_member(&target_user, event) } Invite => { - debug!("handle_membership: User {} is now invited to the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), + debug!( + "handle_membership: User {} is now invited to the room {} ({})", + event.state_key, + self.room_id, + self.display_name(), ); self.add_member(&target_user, event) @@ -903,14 +920,18 @@ impl Room { target_member: &UserId, event: &MemberEvent, ) -> (bool, HashMap) { - let old_name = self.get_member(target_member).map_or_else(|| None, |m| m.display_name.clone()); + let old_name = self + .get_member(target_member) + .map_or_else(|| None, |m| m.display_name.clone()); let new_name = event.content.displayname.clone(); - debug!("update_member_profile [{}]: from nick {:#?} to nick {:#?}", - self.room_id, - old_name, &new_name); + debug!( + "update_member_profile [{}]: from nick {:#?} to nick {:#?}", + self.room_id, old_name, &new_name + ); - let disambiguations = self.disambiguation_updates(target_member, old_name.clone(), new_name.clone()); + let disambiguations = + self.disambiguation_updates(target_member, old_name.clone(), new_name.clone()); for (id, is_ambiguous) in disambiguations.iter() { if self.get_member_mut(id).is_none() { error!("update_member_profile: I'm about to fail for id {}. user_id = {}\nevent = {:#?}", @@ -924,8 +945,7 @@ impl Room { debug!( "update_member_profile [{}]: disambiguations: {:#?}", - self.room_id, - &disambiguations + self.room_id, &disambiguations ); let changed = match self.get_member_mut(target_member) { @@ -937,12 +957,11 @@ impl Room { None => { error!( "update_member_profile [{}]: user {} does not exist", - self.room_id, - target_member + self.room_id, target_member ); false - }, + } }; (changed, disambiguations) diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 98fc4fd7..f508a2b1 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -194,7 +194,10 @@ mod test { .add_room_event(EventsJson::MemberNameChange, RoomEvent::RoomMember) .build_sync_response(); - client.receive_sync_response(&mut initial_response).await.unwrap(); + client + .receive_sync_response(&mut initial_response) + .await + .unwrap(); let room = client.get_joined_room(&room_id).await.unwrap(); @@ -210,7 +213,10 @@ mod test { assert_eq!(member.display_name.as_ref().unwrap(), "example"); } - client.receive_sync_response(&mut name_change_response).await.unwrap(); + client + .receive_sync_response(&mut name_change_response) + .await + .unwrap(); // Afterwards, the display name is "changed". { From 4134ba969a3ea8b4e00b61313edfbf5e51f0ff8c Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Thu, 9 Jul 2020 15:09:55 +0200 Subject: [PATCH 26/83] DRY the membership logging a bit. --- matrix_sdk_base/src/models/room.rs | 121 +++++++++++++++-------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 9736ef0b..d6e9c666 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -626,76 +626,37 @@ impl Room { }; if state_event && !self.member_is_tracked(&target_user) { + debug!( + "handle_membership: User {user_id} is {state} the room {room_id} ({room_name})", + user_id = target_user, + state = event.content.membership.describe(), + room_id = self.room_id, + room_name = self.display_name(), + ); + match event.content.membership { - Join => { - debug!( - "handle_membership: User {} is now a member of the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), - ); - - self.add_member(&target_user, event) - } - - Invite => { - debug!( - "handle_membership: User {} is now invited to the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), - ); - - self.add_member(&target_user, event) - } + Join | Invite => self.add_member(&target_user, event), // We are not interested in tracking past members for now _ => (false, HashMap::new()), } } else { - match event.membership_change() { - Invited => { - debug!( - "handle_membership: User {} got invited to the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), - ); + let change = event.membership_change(); - self.add_member(&target_user, event) - } - Joined => { - debug!( - "handle_membership: User {} joins the room {} ({})", - event.state_key, - self.room_id, - self.display_name(), - ); + debug!( + "handle_membership: User {user_id} {action} the room {room_id} ({room_name})", + user_id = target_user, + action = change.describe(), + room_id = self.room_id, + room_name = self.display_name(), + ); - self.add_member(&target_user, event) - } + match change { + Invited | Joined => self.add_member(&target_user, event), Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { - debug!( - "handle_membership: User {} exiting room {} ({}) (leaving, kicked, banned or invitation rejected)", - event.state_key, - self.room_id, - self.display_name(), - ); - self.remove_member(&target_user, event) } - ProfileChanged => { - debug!( - "handle_membership: Profile changed for user {} in room {} ({}) (displayname={:?}, avatar={:?}", - event.state_key, - self.room_id, - self.display_name(), - event.content.displayname, - event.content.avatar_url - ); - - self.update_member_profile(&target_user, event) - } + ProfileChanged => self.update_member_profile(&target_user, event), // Not interested in other events. _ => (false, HashMap::new()), @@ -997,6 +958,48 @@ impl Room { } } +trait Describe { + fn describe(&self) -> String; +} + +impl Describe for MembershipState { + fn describe(&self) -> String { + use MembershipState::*; + + match self { + Ban => "is banned in", + Invite => "is invited to", + Join => "is a member of", + Knock => "is requesting access", + Leave => "left", + } + .to_string() + } +} + +impl Describe for MembershipChange { + fn describe(&self) -> String { + use MembershipChange::*; + + match self { + Invited => "got invited to", + Joined => "joined", + Kicked => "got kicked from", + Banned => "got banned from", + Unbanned => "got unbanned from", + KickedAndBanned => "got kicked and banned from", + InvitationRejected => "rejected the invitation to", + InvitationRevoked => "got their invitation revoked from", + Left => "left", + ProfileChanged => "changed their profile", + None => "did nothing in", + NotImplemented => "NOT IMPLEMENTED", + Error => "ERROR", + } + .to_string() + } +} + #[cfg(test)] mod test { use super::*; From 8daa12ac56cdb092f53496d49907019606b268ac Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 10 Jul 2020 10:21:41 +0200 Subject: [PATCH 27/83] Print error when receiving invalid response in sync_forever. --- matrix_sdk/src/client.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index be083ad1..f06663ae 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -30,8 +30,7 @@ use matrix_sdk_common::uuid::Uuid; use futures_timer::Delay as sleep; use std::future::Future; #[cfg(feature = "encryption")] -use tracing::{debug, warn}; -use tracing::{info, instrument, trace}; +use tracing::{debug, error, info, instrument, trace, warn}; use http::Method as HttpMethod; use http::Response as HttpResponse; @@ -1294,6 +1293,9 @@ impl Client { let response = if let Ok(r) = response { r } else { + let err = response.unwrap_err(); + error!("Received an invalid response: {}", err); + sleep::new(Duration::from_secs(1)).await; continue; From 05a41d3b4dd0d2170a3dd0aac27cd20e10c05ab9 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Fri, 10 Jul 2020 15:47:11 +0200 Subject: [PATCH 28/83] Move and rename member_display_name to RoomMember::disambiguated_name. This makes more sense as all the required information is now available on `RoomMember`. We also don't have to handle the case of the missing member since now you have to actually get a `RoomMember` before you can ask for their name. --- matrix_sdk_base/src/models/room.rs | 51 +++++------------------ matrix_sdk_base/src/models/room_member.rs | 32 ++++++++++++-- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index d6e9c666..d2adbe79 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; @@ -319,34 +318,6 @@ impl Room { self.encrypted.as_ref() } - /// Get the disambiguated display name for a member of this room. - /// - /// If a member has no display name set, returns the MXID as a fallback. - /// - /// This method never fails, even if user with the supplied MXID is not a member in the room. - /// In this case we still return the supplied MXID as a fallback. - /// - /// When displaying a room member's display name, clients *must* use this method to obtain the - /// name instead of displaying the `RoomMember::display_name` directly. This is because - /// multiple members can share the same display name in which case the display name has to be - /// disambiguated. - pub fn member_display_name<'a>(&'a self, id: &'a UserId) -> Cow<'a, str> { - let member = self.get_member(id); - - match member { - Some(member) => { - if member.display_name_ambiguous { - member.unique_name().into() - } else { - member.name().into() - } - } - - // Even if there is no such member, we return the MXID that was given to us. - None => id.as_ref().into(), - } - } - /// Process the join or invite event for a new room member. /// /// This method should be called only on events which add new members, not on existing ones. @@ -1347,7 +1318,7 @@ mod test { { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); assert_eq!("example", display_name1); } @@ -1366,9 +1337,9 @@ mod test { { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); - let display_name2 = room.member_display_name(&user_id2); - let display_name3 = room.member_display_name(&user_id3); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); + let display_name2 = room.get_member(&user_id2).unwrap().disambiguated_name(); + let display_name3 = room.get_member(&user_id3).unwrap().disambiguated_name(); assert_eq!(format!("example ({})", user_id1), display_name1); assert_eq!(format!("example ({})", user_id2), display_name2); @@ -1385,7 +1356,7 @@ mod test { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); assert_eq!("example", display_name1); } @@ -1401,8 +1372,8 @@ mod test { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); - let display_name2 = room.member_display_name(&user_id2); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); + let display_name2 = room.get_member(&user_id2).unwrap().disambiguated_name(); assert_eq!(format!("example ({})", user_id1), display_name1); assert_eq!(format!("example ({})", user_id2), display_name2); @@ -1419,8 +1390,8 @@ mod test { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); - let display_name2 = room.member_display_name(&user_id2); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); + let display_name2 = room.get_member(&user_id2).unwrap().disambiguated_name(); assert_eq!("changed", display_name1); assert_eq!("example", display_name2); @@ -1437,8 +1408,8 @@ mod test { let room = client.get_joined_room(&room_id).await.unwrap(); let room = room.read().await; - let display_name1 = room.member_display_name(&user_id1); - let display_name2 = room.member_display_name(&user_id2); + let display_name1 = room.get_member(&user_id1).unwrap().disambiguated_name(); + let display_name2 = room.get_member(&user_id2).unwrap().disambiguated_name(); assert_eq!(format!("changed ({})", user_id1), display_name1); assert_eq!(format!("changed ({})", user_id2), display_name2); diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index f508a2b1..1be90dfb 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -105,7 +105,8 @@ impl RoomMember { } } - /// Returns the most ergonomic name available for the member. + /// Returns the most ergonomic (but potentially ambiguous/non-unique) name available for the + /// member. /// /// This is the member's display name if it is set, otherwise their MXID. pub fn name(&self) -> String { @@ -114,16 +115,39 @@ impl RoomMember { .unwrap_or_else(|| format!("{}", self.user_id)) } - /// Returns a name for the member which is guaranteed to be unique. + /// Returns a name for the member which is guaranteed to be unique, but not necessarily the + /// most ergonomic. /// - /// This is either of the format "DISPLAY_NAME (MXID)" if the display name is set for the - /// member, or simply "MXID" if not. + /// This is either a name in the format "DISPLAY_NAME (MXID)" if the member's display name is + /// set, or simply "MXID" if not. pub fn unique_name(&self) -> String { self.display_name .clone() .map(|d| format!("{} ({})", d, self.user_id)) .unwrap_or_else(|| format!("{}", self.user_id)) } + + /// Get the disambiguated display name for the member which is as ergonomic as possible while + /// still guaranteeing it is unique. + /// + /// If the member's display name is currently ambiguous (i.e. shared by other room members), + /// this method will return the same result as `RoomMember::unique_name`. Otherwise, this + /// method will return the same result as `RoomMember::name`. + /// + /// This is usually the name you want when showing room messages from the member or when + /// showing the member in the member list. + /// + /// **Warning**: When displaying a room member's display name, clients *must* use + /// a disambiguated name, so they *must not* use `RoomMember::display_name` directly. Clients + /// *should* use this method to obtain the name, but an acceptable alternative is to use + /// `RoomMember::unique_name` in certain situations. + pub fn disambiguated_name(&self) -> String { + if self.display_name_ambiguous { + self.unique_name().into() + } else { + self.name().into() + } + } } #[cfg(test)] From 7d9a699d62ef10cfb6911e03e3f33391a0ffe97f Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 14 Jul 2020 11:06:31 +0200 Subject: [PATCH 29/83] Fix EventBuilder docstring example. --- matrix_sdk_test/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index dec775bc..3755e5d7 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -58,15 +58,14 @@ pub enum EventsJson { /// /// ```rust /// use matrix_sdk_test::{EventBuilder, EventsJson}; -/// use matrix_sdk_common::events::collections::all::RoomEvent; /// /// let mut builder = EventBuilder::new(); /// /// // response1 now contains events that add an example member to the room and change their power /// // level /// let response1 = builder -/// .add_room_event(EventsJson::Member, RoomEvent::RoomMember) -/// .add_room_event(EventsJson::PowerLevels, RoomEvent::RoomPowerLevels) +/// .add_room_event(EventsJson::Member) +/// .add_room_event(EventsJson::PowerLevels) /// .build_sync_response(); /// /// // response2 is now empty (nothing changed) @@ -74,7 +73,7 @@ pub enum EventsJson { /// /// // response3 contains a display name change for member example /// let response3 = builder -/// .add_room_event(EventsJson::MemberNameChange, RoomEvent::RoomMember) +/// .add_room_event(EventsJson::MemberNameChange) /// .build_sync_response(); /// ``` From 9e48b7172b5f41c0d50322e4bbfc7faed5d97d81 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 14 Jul 2020 12:38:55 +0200 Subject: [PATCH 30/83] cargo fmt --- matrix_sdk_base/src/models/room.rs | 54 ++++++++++++++-------------- matrix_sdk_test/src/test_json/mod.rs | 6 ++-- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 9a76b5cb..d6a2f908 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -648,7 +648,7 @@ impl Room { Kicked | Banned | KickedAndBanned | InvitationRejected | Left => { self.remove_member(&target_user, event) } - ProfileChanged{..} => self.update_member_profile(&target_user, event, change), + ProfileChanged { .. } => self.update_member_profile(&target_user, event, change), // Not interested in other events. _ => (false, HashMap::new()), @@ -909,13 +909,11 @@ impl Room { &mut self, target_member: &UserId, event: &StateEventStub, - change: MembershipChange + change: MembershipChange, ) -> (bool, HashMap) { let member = self.get_member(target_member); let member = match member { - Some(member) => { - member - } + Some(member) => member, None => { debug!("update_member_profile [{}]: Got a profile update for user {} but he's not a room member", @@ -928,25 +926,32 @@ impl Room { let new_name = event.content.displayname.clone(); match change { - MembershipChange::ProfileChanged { displayname_changed, avatar_url_changed } => { + MembershipChange::ProfileChanged { + displayname_changed, + avatar_url_changed, + } => { if displayname_changed { - debug!("update_member_profile [{}]: {} changed display name from {:#?} to {:#?}", + debug!( + "update_member_profile [{}]: {} changed display name from {:#?} to {:#?}", self.room_id, target_member, old_name, &new_name ); } if avatar_url_changed { - debug!("update_member_profile [{}]: {} changed avatar URL from {:#?} to {:#?}", + debug!( + "update_member_profile [{}]: {} changed avatar URL from {:#?} to {:#?}", self.room_id, target_member, &member.avatar_url, &new_name ); } } _ => { - error!("update_member_profile [{}]: got a ProfileChanged but nothing changed", - self.room_id); + error!( + "update_member_profile [{}]: got a ProfileChanged but nothing changed", + self.room_id + ); return (false, HashMap::new()); - }, + } } let disambiguations = @@ -1007,8 +1012,7 @@ impl Room { } if max_power > int!(0) { - member.power_level_norm = - Some((member.power_level.unwrap() * int!(100)) / max_power); + member.power_level_norm = Some((member.power_level.unwrap() * int!(100)) / max_power); } changed @@ -1045,15 +1049,16 @@ impl Describe for MembershipChange { Self::InvitationRejected => "rejected the invitation to", Self::InvitationRevoked => "got their invitation revoked from", Self::Left => "left", - Self::ProfileChanged { displayname_changed, avatar_url_changed } => { - match (*displayname_changed, *avatar_url_changed) { - (true, true) => "changed their displayname and avatar", - (true, false) => "changed their displayname", - (false, true) => "changed their avatar", - _ => { - error!("Got ProfileChanged but nothing changed"); - "impossible: changed nothing in their profile" - }, + Self::ProfileChanged { + displayname_changed, + avatar_url_changed, + } => match (*displayname_changed, *avatar_url_changed) { + (true, true) => "changed their displayname and avatar", + (true, false) => "changed their displayname", + (false, true) => "changed their avatar", + _ => { + error!("Got ProfileChanged but nothing changed"); + "impossible: changed nothing in their profile" } }, Self::None => "did nothing in", @@ -1386,10 +1391,7 @@ mod test { .build_sync_response(); let mut member2_rejoins_when_invited_sync_response = event_builder - .add_custom_joined_event( - &room_id, - member1_invites_member2_event, - ) + .add_custom_joined_event(&room_id, member1_invites_member2_event) .add_custom_joined_event(&room_id, member2_join_event) .build_sync_response(); diff --git a/matrix_sdk_test/src/test_json/mod.rs b/matrix_sdk_test/src/test_json/mod.rs index d8ea7da8..b85e0bad 100644 --- a/matrix_sdk_test/src/test_json/mod.rs +++ b/matrix_sdk_test/src/test_json/mod.rs @@ -9,8 +9,8 @@ pub mod sync; pub use events::{ ALIAS, ALIASES, EVENT_ID, KEYS_QUERY, KEYS_UPLOAD, LOGIN, LOGIN_RESPONSE_ERR, LOGOUT, MEMBER, - MEMBER_NAME_CHANGE, MESSAGE_EDIT, MESSAGE_TEXT, NAME, POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, REACTION, REDACTED, - REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR, ROOM_ID, ROOM_MESSAGES, - TYPING, + MEMBER_NAME_CHANGE, MESSAGE_EDIT, MESSAGE_TEXT, NAME, POWER_LEVELS, PRESENCE, PUBLIC_ROOMS, + REACTION, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR, + ROOM_ID, ROOM_MESSAGES, TYPING, }; pub use sync::{DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC}; From 32737a5517da3b324ed02f34fc7e415fa2e93eb4 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 09:43:58 +0200 Subject: [PATCH 31/83] Use match instead of if-let in sync_forever. --- matrix_sdk/src/client.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 456ba5d1..a560d4cf 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1310,15 +1310,13 @@ impl Client { loop { let response = self.sync(sync_settings.clone()).await; - let response = if let Ok(r) = response { - r - } else { - let err = response.unwrap_err(); - error!("Received an invalid response: {}", err); - - sleep::new(Duration::from_secs(1)).await; - - continue; + let response = match response { + Ok(r) => r, + Err(e) => { + error!("Received an invalid response: {}", e); + sleep::new(Duration::from_secs(1)).await; + continue; + } }; // TODO send out to-device messages here From ea149ebd8eb69e051c48d8d2fe51aeb54bbf3fb0 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 11:16:13 +0200 Subject: [PATCH 32/83] Update docstring for disambiguation_updates. --- matrix_sdk_base/src/models/room.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index d6a2f908..4b4b73a6 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -478,13 +478,25 @@ impl Room { .collect() } - /// Given a room member, generate a map of all display name disambiguations which are necessary - /// in order to make that member's display name unique. + /// Answers the question "If `member` changed their display name from + /// `old_name` to `new_name`, which members' display names would become + /// ambiguous and which would no longer be ambiguous?". /// - /// The `inclusive` parameter controls whether or not the member for which we are - /// disambiguating should be considered a current member of the room. + /// Returns the map of ambiguity status changes for those members which + /// would be affected by the change. /// - /// Returns a map from MXID to disambiguated name. + /// It is important that this method be called *before* any changes are made + /// to the model, i.e. before any actual display name changes. + /// + /// # Arguments + /// + /// - `member`: The MXID of the member who is changing their display name. + /// - `old_name`: The old display name of `member`. May be `None` if + /// `member` had no display name in the room before (because he had not + /// set it or he is just entering the room). + /// - `new_name`: The new display name of `member`. May be `None` if + /// `member` will no longer have a display name in the room after the + /// change (because he is removing it or exiting the room). fn disambiguation_updates( &self, member: &UserId, From 62943f055d97b45db1fd4f3393816f66313bae20 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 11:21:01 +0200 Subject: [PATCH 33/83] Rewrap docstrings and comments to 80 chars. --- matrix_sdk_base/src/models/room.rs | 67 ++++++++++++----------- matrix_sdk_base/src/models/room_member.rs | 37 +++++++------ 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 4b4b73a6..4a8f5e20 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -71,20 +71,20 @@ fn redaction_event_from_redaction_stub( pub struct RoomName { /// The displayed name of the room. name: Option, - /// The canonical alias of the room ex. `#room-name:example.com` and port number. + /// The canonical alias of the room ex. `#room-name:example.com` and port + /// number. canonical_alias: Option, /// List of `RoomAliasId`s the room has been given. aliases: Vec, - /// Users which can be used to generate a room name if the room does not have - /// one. Required if room name or canonical aliases are not set or empty. + /// Users which can be used to generate a room name if the room does not + /// have one. Required if room name or canonical aliases are not set or + /// empty. pub heroes: Vec, - /// Number of users whose membership status is `join`. - /// Required if field has changed since last sync; otherwise, it may be - /// omitted. + /// Number of users whose membership status is `join`. Required if field + /// has changed since last sync; otherwise, it may be omitted. pub joined_member_count: Option, - /// Number of users whose membership status is `invite`. - /// Required if field has changed since last sync; otherwise, it may be - /// omitted. + /// Number of users whose membership status is `invite`. Required if field + /// has changed since last sync; otherwise, it may be omitted. pub invited_member_count: Option, } @@ -185,8 +185,8 @@ pub struct Room { pub joined_members: HashMap, /// A queue of messages, holds no more than 10 of the most recent messages. /// - /// This is helpful when using a `StateStore` to avoid multiple requests - /// to the server for messages. + /// This is helpful when using a `StateStore` to avoid multiple requests to + /// the server for messages. #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] #[serde(with = "super::message::ser_deser")] @@ -221,8 +221,8 @@ impl RoomName { true } - /// Calculate the canonical display name of a room, taking into account its name, aliases and - /// members. + /// Calculate the canonical display name of the room, taking into account + /// its name, aliases and members. /// /// The display name is calculated according to [this algorithm][spec]. /// @@ -276,7 +276,8 @@ impl RoomName { .collect::>(); names.sort(); - // TODO: What length does the spec want us to use here and in the `else`? + // TODO: What length does the spec want us to use here and in + // the `else`? format!("{}, and {} others", names.join(", "), (joined + invited)) } else { "Empty room".to_string() @@ -335,14 +336,14 @@ impl Room { /// Process the join or invite event for a new room member. /// - /// This method should only be called on events which add new members, not those related to - /// existing ones. + /// This method should only be called on events which add new members, not + /// those related to existing ones. /// /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. Returns a map of display name disambiguations which tells us which members need to have - /// their display names disambiguated and to what. + /// 2. Returns a map of display name disambiguations which tells us which + /// members need to have their display names disambiguated and to what. /// /// # Arguments /// @@ -395,8 +396,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. Returns a map of display name disambiguations which tells us which members need to have - /// their display names disambiguated and to what. + /// 2. Returns a map of display name disambiguations which tells us which + /// members need to have their display names disambiguated and to what. /// /// # Arguments /// @@ -432,7 +433,8 @@ impl Room { (true, disambiguations) } - /// Check whether the user with the MXID `user_id` is joined or invited to the room. + /// Check whether the user with the MXID `user_id` is joined or invited to + /// the room. /// /// Returns true if so, false otherwise. pub fn member_is_tracked(&self, user_id: &UserId) -> bool { @@ -605,8 +607,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the joined member list changed, false otherwise. - /// 2. A map of display name disambiguations which tells us which members need to have their - /// display names disambiguated and to what. + /// 2. A map of display name disambiguations which tells us which members + /// need to have their display names disambiguated and to what. pub fn handle_membership( &mut self, event: &StateEventStub, @@ -681,8 +683,9 @@ impl Room { /// Handle a room.redaction event and update the `MessageQueue`. /// /// Returns true if `MessageQueue` was updated. The effected message event - /// has it's contents replaced with the `RedactionEventContents` and the whole - /// redaction event is added to the `Unsigned` `redacted_because` field. + /// has it's contents replaced with the `RedactionEventContents` and the + /// whole redaction event is added to the `Unsigned` `redacted_because` + /// field. #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] pub fn handle_redaction(&mut self, event: &RedactionEventStub) -> bool { @@ -713,7 +716,8 @@ impl Room { } } - /// Handle a room.canonical_alias event, updating the room state if necessary. + /// Handle a room.canonical_alias event, updating the room state if + /// necessary. /// /// Returns true if the room name changed, false otherwise. pub fn handle_canonical(&mut self, event: &StateEventStub) -> bool { @@ -845,8 +849,8 @@ impl Room { /// /// # Arguments /// - /// * `event` - The `AnyStrippedStateEvent` sent by the server for invited but not - /// joined rooms. + /// * `event` - The `AnyStrippedStateEvent` sent by the server for invited + /// but not joined rooms. pub fn receive_stripped_state_event(&mut self, event: &AnyStrippedStateEventStub) -> bool { match event { AnyStrippedStateEventStub::RoomName(event) => self.handle_stripped_room_name(event), @@ -856,7 +860,8 @@ impl Room { /// Receive a presence event for a member of the current room. /// - /// Returns true if the event causes a change to the member's presence, false otherwise. + /// Returns true if the event causes a change to the member's presence, + /// false otherwise. /// /// # Arguments /// @@ -910,8 +915,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. A map of display name disambiguations which tells us which members need to have their - /// display names disambiguated and to what. + /// 2. A map of display name disambiguations which tells us which members + /// need to have their display names disambiguated and to what. /// /// # Arguments /// diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 5a70487c..de0a50f9 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -32,7 +32,8 @@ pub struct RoomMember { pub user_id: UserId, /// The human readable name of the user. pub display_name: Option, - /// Whether the member's display name is ambiguous due to being shared with other members. + /// Whether the member's display name is ambiguous due to being shared with + /// other members. pub display_name_ambiguous: bool, /// The matrix url of the users avatar. pub avatar_url: Option, @@ -102,8 +103,8 @@ impl RoomMember { } } - /// Returns the most ergonomic (but potentially ambiguous/non-unique) name available for the - /// member. + /// Returns the most ergonomic (but potentially ambiguous/non-unique) name + /// available for the member. /// /// This is the member's display name if it is set, otherwise their MXID. pub fn name(&self) -> String { @@ -112,11 +113,11 @@ impl RoomMember { .unwrap_or_else(|| format!("{}", self.user_id)) } - /// Returns a name for the member which is guaranteed to be unique, but not necessarily the - /// most ergonomic. + /// Returns a name for the member which is guaranteed to be unique, but not + /// necessarily the most ergonomic. /// - /// This is either a name in the format "DISPLAY_NAME (MXID)" if the member's display name is - /// set, or simply "MXID" if not. + /// This is either a name in the format "DISPLAY_NAME (MXID)" if the + /// member's display name is set, or simply "MXID" if not. pub fn unique_name(&self) -> String { self.display_name .clone() @@ -124,19 +125,21 @@ impl RoomMember { .unwrap_or_else(|| format!("{}", self.user_id)) } - /// Get the disambiguated display name for the member which is as ergonomic as possible while - /// still guaranteeing it is unique. + /// Get the disambiguated display name for the member which is as ergonomic + /// as possible while still guaranteeing it is unique. /// - /// If the member's display name is currently ambiguous (i.e. shared by other room members), - /// this method will return the same result as `RoomMember::unique_name`. Otherwise, this - /// method will return the same result as `RoomMember::name`. + /// If the member's display name is currently ambiguous (i.e. shared by + /// other room members), this method will return the same result as + /// `RoomMember::unique_name`. Otherwise, this method will return the same + /// result as `RoomMember::name`. /// - /// This is usually the name you want when showing room messages from the member or when - /// showing the member in the member list. + /// This is usually the name you want when showing room messages from the + /// member or when showing the member in the member list. /// - /// **Warning**: When displaying a room member's display name, clients *must* use - /// a disambiguated name, so they *must not* use `RoomMember::display_name` directly. Clients - /// *should* use this method to obtain the name, but an acceptable alternative is to use + /// **Warning**: When displaying a room member's display name, clients + /// *must* use a disambiguated name, so they *must not* use + /// `RoomMember::display_name` directly. Clients *should* use this method to + /// obtain the name, but an acceptable alternative is to use /// `RoomMember::unique_name` in certain situations. pub fn disambiguated_name(&self) -> String { if self.display_name_ambiguous { From 8a4a4140b34cb36bd8adb1ba0a6f793d1af18fc0 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 11:22:47 +0200 Subject: [PATCH 34/83] Remove stale comment. --- matrix_sdk_base/src/models/room.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 4a8f5e20..1d120128 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -505,8 +505,6 @@ impl Room { old_name: Option, new_name: Option, ) -> HashMap { - // Must be called *before* any changes to the model. - let old_name_eq_class = match old_name { None => HashSet::new(), Some(name) => self.display_name_equivalence_class(&name), From 1fd21ee206d445d382dc056109e02f4fbc3ad82d Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 11:54:30 +0200 Subject: [PATCH 35/83] Fix docstrings regarding return value related to disambiguation. --- matrix_sdk_base/src/models/room.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 1d120128..05e347dc 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -342,8 +342,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. Returns a map of display name disambiguations which tells us which - /// members need to have their display names disambiguated and to what. + /// 2. A map of display name ambiguity status changes (see + /// `disambiguation_updates`). /// /// # Arguments /// @@ -396,8 +396,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. Returns a map of display name disambiguations which tells us which - /// members need to have their display names disambiguated and to what. + /// 2. A map of display name ambiguity status changes (see + /// `disambiguation_updates`). /// /// # Arguments /// @@ -605,8 +605,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the joined member list changed, false otherwise. - /// 2. A map of display name disambiguations which tells us which members - /// need to have their display names disambiguated and to what. + /// 2. A map of display name ambiguity status changes (see + /// `disambiguation_updates`). pub fn handle_membership( &mut self, event: &StateEventStub, @@ -913,8 +913,8 @@ impl Room { /// Returns a tuple of: /// /// 1. True if the event made changes to the room's state, false otherwise. - /// 2. A map of display name disambiguations which tells us which members - /// need to have their display names disambiguated and to what. + /// 2. A map of display name ambiguity status changes (see + /// `disambiguation_updates`). /// /// # Arguments /// From bce7fe0217358bde425e267d04fa965790b039e5 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Wed, 15 Jul 2020 11:58:52 +0200 Subject: [PATCH 36/83] Equivalence class -> equivalence set. --- matrix_sdk_base/src/models/room.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 05e347dc..7151724b 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -461,7 +461,7 @@ impl Room { } /// Given a display name, return the set of members which share it. - fn display_name_equivalence_class(&self, name: &str) -> HashSet { + fn display_name_equivalence_set(&self, name: &str) -> HashSet { let members = self .invited_members .iter() @@ -505,30 +505,30 @@ impl Room { old_name: Option, new_name: Option, ) -> HashMap { - let old_name_eq_class = match old_name { + let old_name_eq_set = match old_name { None => HashSet::new(), - Some(name) => self.display_name_equivalence_class(&name), + Some(name) => self.display_name_equivalence_set(&name), }; - let disambiguate_old = match old_name_eq_class.len().saturating_sub(1) { + let disambiguate_old = match old_name_eq_set.len().saturating_sub(1) { n if n > 1 => vec![(member.clone(), false)].into_iter().collect(), - 1 => old_name_eq_class.into_iter().map(|m| (m, false)).collect(), + 1 => old_name_eq_set.into_iter().map(|m| (m, false)).collect(), 0 => HashMap::new(), _ => panic!("impossible"), }; // - let mut new_name_eq_class = match new_name { + let mut new_name_eq_set = match new_name { None => HashSet::new(), - Some(name) => self.display_name_equivalence_class(&name), + Some(name) => self.display_name_equivalence_set(&name), }; - new_name_eq_class.insert(member.clone()); + new_name_eq_set.insert(member.clone()); - let disambiguate_new = match new_name_eq_class.len() { + let disambiguate_new = match new_name_eq_set.len() { 1 => HashMap::new(), - 2 => new_name_eq_class.into_iter().map(|m| (m, true)).collect(), + 2 => new_name_eq_set.into_iter().map(|m| (m, true)).collect(), _ => vec![(member.clone(), true)].into_iter().collect(), }; From 83806b42e96d9be7ba1265c388aed65fe85f7213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 13:07:48 +0200 Subject: [PATCH 37/83] crypto: Remove a stale comment about clearing private keys from events. --- matrix_sdk_crypto/src/machine.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 58d30c39..102fd62b 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -1150,8 +1150,6 @@ impl OlmMachine { } }; - // TODO make sure private keys are cleared from the event - // before we replace the result. *event_result = decrypted_event; } AnyToDeviceEvent::RoomKeyRequest(e) => self.handle_room_key_request(e), From 204279c5756f6f4123c19d3915d0b52c53b52b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 13:19:56 +0200 Subject: [PATCH 38/83] matrix-sdk: Don't hide the tracing import behind the encryption feature. --- matrix_sdk/src/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index a560d4cf..99005340 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -29,7 +29,6 @@ use matrix_sdk_common::uuid::Uuid; use futures_timer::Delay as sleep; use std::future::Future; -#[cfg(feature = "encryption")] use tracing::{debug, error, info, instrument, trace, warn}; use http::Method as HttpMethod; From 3315cf5bc6a2a17e7400e05e3a9d62d25bf665f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 13:23:28 +0200 Subject: [PATCH 39/83] travis: Add a build that doesn't use any of the features. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0cc5a004..761e8f82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,11 @@ jobs: - os: osx + - os: linux + name: Minimal build + script: + - cargo build --no-default-features + - os: linux name: Coverage before_script: From bf152df3224ad0b1451b1e02dd52c51e8d712bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 14:05:22 +0200 Subject: [PATCH 40/83] matrix-sdk: Hide some tracing imports behind the encryption flag. --- matrix_sdk/src/client.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 99005340..af962e7a 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -29,7 +29,9 @@ use matrix_sdk_common::uuid::Uuid; use futures_timer::Delay as sleep; use std::future::Future; -use tracing::{debug, error, info, instrument, trace, warn}; +#[cfg(feature = "encryption")] +use tracing::{debug, warn}; +use tracing::{error, info, instrument, trace}; use http::Method as HttpMethod; use http::Response as HttpResponse; From 4e40c13b814f21b02a1fc8af2b9ae3e710003c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 14:05:52 +0200 Subject: [PATCH 41/83] travis: Fix the minimal build. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 761e8f82..8a0d4840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ jobs: - os: linux name: Minimal build script: + - cd matrix_sdk - cargo build --no-default-features - os: linux From 6cced25ae1e957321cca355bcf4374a1b8f4b6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 14:13:43 +0200 Subject: [PATCH 42/83] travis: Don't allow failures on the wasm target. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a0d4840..48a1db0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ addons: jobs: allow_failures: - os: windows - - os: linux - name: wasm32-unknown-unknown include: - stage: Lint From de1988265deac2e2eec49d4576af5a5e75760c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 15:39:56 +0200 Subject: [PATCH 43/83] crypto: Move the outbound session creation logic into the account. --- matrix_sdk_crypto/src/error.rs | 24 +++++++++ matrix_sdk_crypto/src/machine.rs | 75 ++++++--------------------- matrix_sdk_crypto/src/olm.rs | 70 +++++++++++++++++++++++-- matrix_sdk_crypto/src/store/sqlite.rs | 2 +- 4 files changed, 108 insertions(+), 63 deletions(-) diff --git a/matrix_sdk_crypto/src/error.rs b/matrix_sdk_crypto/src/error.rs index 09895a6d..4dcc207b 100644 --- a/matrix_sdk_crypto/src/error.rs +++ b/matrix_sdk_crypto/src/error.rs @@ -13,6 +13,7 @@ // limitations under the License. use cjson::Error as CjsonError; +use matrix_sdk_common::identifiers::{DeviceId, UserId}; use olm_rs::errors::{OlmGroupSessionError, OlmSessionError}; use serde_json::Error as SerdeError; use thiserror::Error; @@ -121,6 +122,29 @@ pub enum SignatureError { VerificationError, } +#[derive(Error, Debug)] +pub(crate) enum SessionCreationError { + #[error( + "Failed to create a new Olm session for {0} {1}, the requested \ + one-time key isn't a signed curve key" + )] + OneTimeKeyNotSigned(UserId, DeviceId), + #[error( + "Tried to create a new Olm session for {0} {1}, but the signed \ + one-time key is missing" + )] + OneTimeKeyMissing(UserId, DeviceId), + #[error("Failed to verify the one-time key signatures for {0} {1}: {2:?}")] + InvalidSignature(UserId, DeviceId, SignatureError), + #[error( + "Tried to create an Olm session for {0} {1}, but the device is missing \ + a curve25519 key" + )] + DeviceMissingCurveKey(UserId, DeviceId), + #[error("Error creating new Olm session for {0} {1}: {2:?}")] + OlmError(UserId, DeviceId, OlmSessionError), +} + impl From for SignatureError { fn from(error: CjsonError) -> Self { Self::CanonicalJsonError(error) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 102fd62b..f0305cfa 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -306,76 +306,35 @@ impl OlmMachine { for (user_id, user_devices) in &response.one_time_keys { for (device_id, key_map) in user_devices { - let device = if let Some(d) = self - .store - .get_device(&user_id, device_id) - .await - .expect("Can't get devices") - { - d - } else { - warn!( - "Tried to create an Olm session for {} {}, but the device is unknown", - user_id, device_id - ); - continue; - }; - - // TODO move this logic into the account, pass the device to the - // account when creating an outbound session. - let one_time_key = if let Some(k) = key_map.values().next() { - match k { - OneTimeKey::SignedKey(k) => k, - OneTimeKey::Key(_) => { + let device: Device = match self.store.get_device(&user_id, device_id).await { + Ok(d) => { + if let Some(d) = d { + d + } else { warn!( - "Tried to create an Olm session for {} {}, but - the requested key isn't a signed curve key", + "Tried to create an Olm session for {} {}, but \ + the device is unknown", user_id, device_id ); continue; } } - } else { - warn!( - "Tried to create an Olm session for {} {}, but the - signed one-time key is missing", - user_id, device_id - ); - continue; - }; - - if let Err(e) = device.verify_one_time_key(&one_time_key) { - warn!( - "Failed to verify the one-time key signatures for {} {}: {:?}", - user_id, device_id, e - ); - continue; - } - - let curve_key = if let Some(k) = device.get_key(KeyAlgorithm::Curve25519) { - k - } else { - warn!( - "Tried to create an Olm session for {} {}, but the - device is missing the curve key", - user_id, device_id - ); - continue; + Err(e) => { + warn!( + "Tried to create an Olm session for {} {}, but \ + can't fetch the device from the store {:?}", + user_id, device_id, e + ); + continue; + } }; info!("Creating outbound Session for {} {}", user_id, device_id); - let session = match self - .account - .create_outbound_session(curve_key, &one_time_key) - .await - { + let session = match self.account.create_outbound_session(device, &key_map).await { Ok(s) => s, Err(e) => { - warn!( - "Error creating new Olm session for {} {}: {}", - user_id, device_id, e - ); + warn!("{:?}", e); continue; } }; diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index e1e33a20..e17b2e65 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -33,7 +33,8 @@ use olm_rs::outbound_group_session::OlmOutboundGroupSession; use olm_rs::session::OlmSession; use olm_rs::PicklingMode; -use crate::error::{EventError, MegolmResult}; +use crate::device::Device; +use crate::error::{EventError, MegolmResult, SessionCreationError}; pub use olm_rs::{ session::{OlmMessage, PreKeyMessage}, utility::OlmUtility, @@ -386,7 +387,7 @@ impl Account { /// /// * `their_one_time_key` - A signed one-time key that the other account /// created and shared with us. - pub(crate) async fn create_outbound_session( + pub(crate) async fn create_outbound_session_helper( &self, their_identity_key: &str, their_one_time_key: &SignedKey, @@ -409,6 +410,67 @@ impl Account { }) } + /// Create a new session with another account given a one-time key and a + /// device. + /// + /// Returns the newly created session or a `OlmSessionError` if creating a + /// session failed. + /// + /// # Arguments + /// * `device` - The other account's device. + /// + /// * `key_map` - A map from the algorithm and device id to the one-time + /// key that the other account created and shared with us. + pub(crate) async fn create_outbound_session( + &self, + device: Device, + key_map: &BTreeMap, + ) -> Result { + let one_time_key = + key_map + .values() + .next() + .ok_or(SessionCreationError::OneTimeKeyMissing( + device.user_id().to_owned(), + device.device_id().to_owned(), + ))?; + + let one_time_key = match one_time_key { + OneTimeKey::SignedKey(k) => k, + OneTimeKey::Key(_) => { + return Err(SessionCreationError::OneTimeKeyNotSigned( + device.user_id().to_owned(), + device.device_id().to_owned(), + )); + } + }; + + device.verify_one_time_key(&one_time_key).map_err(|e| { + SessionCreationError::InvalidSignature( + device.user_id().to_owned(), + device.device_id().to_owned(), + e, + ) + })?; + + let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or( + SessionCreationError::DeviceMissingCurveKey( + device.user_id().to_owned(), + device.device_id().to_owned(), + ), + )?; + + self.create_outbound_session_helper(curve_key, &one_time_key) + .await + .map_err(|e| { + SessionCreationError::OlmError( + device.user_id().to_owned(), + device.device_id().to_owned(), + e, + ) + }) + } + /// Create a new session with another account given a pre-key Olm message. /// /// Returns the newly created session or a `OlmSessionError` if creating a @@ -1014,7 +1076,7 @@ pub(crate) mod test { }; let sender_key = bob.identity_keys().curve25519().to_owned(); let session = alice - .create_outbound_session(&sender_key, &one_time_key) + .create_outbound_session_helper(&sender_key, &one_time_key) .await .unwrap(); @@ -1092,7 +1154,7 @@ pub(crate) mod test { }; let mut bob_session = bob - .create_outbound_session(alice_keys.curve25519(), &one_time_key) + .create_outbound_session_helper(alice_keys.curve25519(), &one_time_key) .await .unwrap(); diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 2287afe9..bfa26664 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -876,7 +876,7 @@ mod test { }; let sender_key = bob.identity_keys().curve25519().to_owned(); let session = alice - .create_outbound_session(&sender_key, &one_time_key) + .create_outbound_session_helper(&sender_key, &one_time_key) .await .unwrap(); From 497b973eb5291a9b1661380e31c7549a339a16eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 6 Jul 2020 10:17:30 +0200 Subject: [PATCH 44/83] travis: Test newer macOS versions. --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 48a1db0b..e7e161b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ addons: jobs: allow_failures: - os: windows + - os: osx + name: macOS 10.15 include: - stage: Lint @@ -31,6 +33,10 @@ jobs: - cd matrix_sdk - cargo build --no-default-features + - os: osx + name: macOS 10.15 + osx_image: xcode12 + - os: linux name: Coverage before_script: From a2a87b9fff1a6427d13feadf8548fca0bffc5b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 15:53:17 +0200 Subject: [PATCH 45/83] matrix-sdk: Fix a bunch of clippy warnings. --- matrix_sdk/src/request_builder.rs | 6 ++++++ matrix_sdk_base/src/models/room.rs | 8 ++++---- matrix_sdk_base/src/models/room_member.rs | 4 ++-- matrix_sdk_crypto/src/machine.rs | 1 + matrix_sdk_crypto/src/olm.rs | 22 +++++++++++----------- matrix_sdk_crypto/src/store/mod.rs | 2 +- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/matrix_sdk/src/request_builder.rs b/matrix_sdk/src/request_builder.rs index 82d4b2ee..d397d78f 100644 --- a/matrix_sdk/src/request_builder.rs +++ b/matrix_sdk/src/request_builder.rs @@ -152,6 +152,12 @@ impl RoomBuilder { } } +impl Default for RoomBuilder { + fn default() -> Self { + Self::new() + } +} + impl Into for RoomBuilder { fn into(mut self) -> create_room::Request { self.req.creation_content = Some(self.creation_content); diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index c0c97cd7..ae1d9c88 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -374,12 +374,12 @@ impl Room { self.invited_members.remove(target_member); self.joined_members - .insert(target_member.clone(), new_member.clone()) + .insert(target_member.clone(), new_member) } MembershipState::Invite => self .invited_members - .insert(target_member.clone(), new_member.clone()), + .insert(target_member.clone(), new_member), _ => panic!("Room::add_member called on event that is neither `join` nor `invite`."), }; @@ -416,7 +416,7 @@ impl Room { // Perform display name disambiguations, if necessary. let disambiguations = - self.disambiguation_updates(target_member, leaving_member.display_name.clone(), None); + self.disambiguation_updates(target_member, leaving_member.display_name, None); debug!("remove_member: disambiguations: {:#?}", disambiguations); @@ -970,7 +970,7 @@ impl Room { } let disambiguations = - self.disambiguation_updates(target_member, old_name.clone(), new_name.clone()); + self.disambiguation_updates(target_member, old_name, new_name.clone()); for (id, is_ambiguous) in disambiguations.iter() { if self.get_member_mut(id).is_none() { debug!("update_member_profile [{}]: Tried disambiguating display name for {} but he's not there", diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index de0a50f9..999ac68c 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -143,9 +143,9 @@ impl RoomMember { /// `RoomMember::unique_name` in certain situations. pub fn disambiguated_name(&self) -> String { if self.display_name_ambiguous { - self.unique_name().into() + self.unique_name() } else { - self.name().into() + self.name() } } } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index f0305cfa..556a599b 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -98,6 +98,7 @@ impl OlmMachine { /// * `user_id` - The unique id of the user that owns this machine. /// /// * `device_id` - The unique id of the device that owns this machine. + #[allow(clippy::ptr_arg)] pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self { OlmMachine { user_id: user_id.clone(), diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index e17b2e65..aece7912 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -87,6 +87,7 @@ impl Account { ]; /// Create a fresh new account, this will generate the identity key-pair. + #[allow(clippy::ptr_arg)] pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self { let account = OlmAccount::new(); let identity_keys = account.parsed_identity_keys(); @@ -252,6 +253,7 @@ impl Account { /// /// * `shared` - Boolean determining if the account was uploaded to the /// server. + #[allow(clippy::ptr_arg)] pub fn from_pickle( pickle: String, pickle_mode: PicklingMode, @@ -426,14 +428,12 @@ impl Account { device: Device, key_map: &BTreeMap, ) -> Result { - let one_time_key = - key_map - .values() - .next() - .ok_or(SessionCreationError::OneTimeKeyMissing( - device.user_id().to_owned(), - device.device_id().to_owned(), - ))?; + let one_time_key = key_map.values().next().ok_or_else(|| { + SessionCreationError::OneTimeKeyMissing( + device.user_id().to_owned(), + device.device_id().to_owned(), + ) + })?; let one_time_key = match one_time_key { OneTimeKey::SignedKey(k) => k, @@ -453,12 +453,12 @@ impl Account { ) })?; - let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or( + let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or_else(|| { SessionCreationError::DeviceMissingCurveKey( device.user_id().to_owned(), device.device_id().to_owned(), - ), - )?; + ) + })?; self.create_outbound_session_helper(curve_key, &one_time_key) .await diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index 7a690ce2..2e40503e 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -87,7 +87,7 @@ pub enum CryptoStoreError { pub type Result = std::result::Result; #[async_trait] -#[warn(clippy::type_complexity)] +#[allow(clippy::type_complexity)] #[cfg_attr(not(target_arch = "wasm32"), send_sync)] /// Trait abstracting a store that the `OlmMachine` uses to store cryptographic /// keys. From 5bebe1d434d583cb591ab9891c9608ce53a0dfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 15:58:36 +0200 Subject: [PATCH 46/83] crypto: Clippy fixes for our tests. --- matrix_sdk_crypto/src/memory_stores.rs | 4 ++-- matrix_sdk_crypto/src/olm.rs | 4 ++-- matrix_sdk_crypto/src/store/memorystore.rs | 4 ++-- matrix_sdk_crypto/src/store/sqlite.rs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index be7aba34..f38f476e 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -292,8 +292,8 @@ mod test { let user_devices = store.user_devices(device.user_id()); - assert_eq!(user_devices.keys().nth(0).unwrap(), device.device_id()); - assert_eq!(user_devices.devices().nth(0).unwrap(), &device); + assert_eq!(user_devices.keys().next().unwrap(), device.device_id()); + assert_eq!(user_devices.devices().next().unwrap(), &device); let loaded_device = user_devices.get(device.device_id()).unwrap(); diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index aece7912..5c4f6f0d 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -1066,7 +1066,7 @@ pub(crate) mod test { .await .curve25519() .iter() - .nth(0) + .next() .unwrap() .1 .to_owned(); @@ -1143,7 +1143,7 @@ pub(crate) mod test { let one_time_key = one_time_keys .curve25519() .iter() - .nth(0) + .next() .unwrap() .1 .to_owned(); diff --git a/matrix_sdk_crypto/src/store/memorystore.rs b/matrix_sdk_crypto/src/store/memorystore.rs index ccef2efd..a911320f 100644 --- a/matrix_sdk_crypto/src/store/memorystore.rs +++ b/matrix_sdk_crypto/src/store/memorystore.rs @@ -200,8 +200,8 @@ mod test { let user_devices = store.get_user_devices(device.user_id()).await.unwrap(); - assert_eq!(user_devices.keys().nth(0).unwrap(), device.device_id()); - assert_eq!(user_devices.devices().nth(0).unwrap(), &device); + assert_eq!(user_devices.keys().next().unwrap(), device.device_id()); + assert_eq!(user_devices.devices().next().unwrap(), &device); let loaded_device = user_devices.get(device.device_id()).unwrap(); diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index bfa26664..cfda0a62 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -866,7 +866,7 @@ mod test { .await .curve25519() .iter() - .nth(0) + .next() .unwrap() .1 .to_owned(); @@ -1165,8 +1165,8 @@ mod test { assert_eq!(device.keys(), loaded_device.keys()); let user_devices = store.get_user_devices(device.user_id()).await.unwrap(); - assert_eq!(user_devices.keys().nth(0).unwrap(), device.device_id()); - assert_eq!(user_devices.devices().nth(0).unwrap(), &device); + assert_eq!(user_devices.keys().next().unwrap(), device.device_id()); + assert_eq!(user_devices.devices().next().unwrap(), &device); } #[tokio::test] From 38166135dce3c127856efb8c983ebd956b8977a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 15:59:04 +0200 Subject: [PATCH 47/83] travis: Add a clippy stage. --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e7e161b2..e7136a1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,21 @@ jobs: name: macOS 10.15 include: - - stage: Lint + - stage: Format os: linux before_script: - rustup component add rustfmt script: - cargo fmt --all -- --check + - stage: Clippy + os: linux + before_script: + - rustup component add clippy + script: + - cargo clippy --all-targets --all-features -- -D warnings + + - stage: Test os: linux dist: bionic From 2d955027e1a5720866eab62e1b18a4dc1f083ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Jul 2020 16:08:21 +0200 Subject: [PATCH 48/83] travis: Don't set the linux version. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e7136a1a..d35afa94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,10 +26,8 @@ jobs: script: - cargo clippy --all-targets --all-features -- -D warnings - - stage: Test os: linux - dist: bionic - os: windows From 0542e3d83d5d808c9bdb9ee3f2d389517300b6b6 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 16 Jul 2020 03:49:46 -0700 Subject: [PATCH 49/83] Client::sync(): expose sync filter --- matrix_sdk/src/client.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index af962e7a..6dc6402b 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1220,9 +1220,13 @@ impl Client { /// /// * `sync_settings` - Settings for the sync call. #[instrument] - pub async fn sync(&self, sync_settings: SyncSettings) -> Result { + pub async fn sync( + &self, + sync_settings: SyncSettings, + filter: Option, + ) -> Result { let request = sync_events::Request { - filter: None, + filter, since: sync_settings.token, full_state: sync_settings.full_state, set_presence: sync_events::SetPresence::Online, @@ -1297,6 +1301,7 @@ impl Client { pub async fn sync_forever( &self, sync_settings: SyncSettings, + filter: Option, callback: impl Fn(sync_events::Response) -> C, ) where C: Future, @@ -1309,7 +1314,7 @@ impl Client { } loop { - let response = self.sync(sync_settings.clone()).await; + let response = self.sync(sync_settings.clone(), None).await; let response = match response { Ok(r) => r, @@ -1561,7 +1566,7 @@ mod test { let room = client.get_joined_room(&room_id).await; assert!(room.is_none()); - client.sync(SyncSettings::default()).await.unwrap(); + client.sync(SyncSettings::default(), None).await.unwrap(); let room = client.get_left_room(&room_id).await; assert!(room.is_none()); @@ -1576,7 +1581,7 @@ mod test { joined_client.restore_login(session).await.unwrap(); // joined room reloaded from state store - joined_client.sync(SyncSettings::default()).await.unwrap(); + joined_client.sync(SyncSettings::default(), None).await.unwrap(); let room = joined_client.get_joined_room(&room_id).await; assert!(room.is_some()); @@ -1588,7 +1593,7 @@ mod test { .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) .create(); - joined_client.sync(SyncSettings::default()).await.unwrap(); + joined_client.sync(SyncSettings::default(), None).await.unwrap(); let room = joined_client.get_joined_room(&room_id).await; assert!(room.is_none()); @@ -1620,7 +1625,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings).await.unwrap(); + let _response = client.sync(sync_settings, None).await.unwrap(); // let bc = &client.base_client; // let ignored_users = bc.ignored_users.read().await; @@ -2243,7 +2248,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings).await.unwrap(); + let _response = client.sync(sync_settings, None).await.unwrap(); let rooms_lock = &client.base_client.joined_rooms(); let rooms = rooms_lock.read().await; @@ -2279,7 +2284,7 @@ mod test { client.restore_login(session).await.unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings).await.unwrap(); + let _response = client.sync(sync_settings, None).await.unwrap(); let mut room_names = vec![]; for room in client.joined_rooms().read().await.values() { @@ -2311,7 +2316,7 @@ mod test { .with_body(test_json::INVITE_SYNC.to_string()) .create(); - let _response = client.sync(SyncSettings::default()).await.unwrap(); + let _response = client.sync(SyncSettings::default(), None).await.unwrap(); assert!(client.joined_rooms().read().await.is_empty()); assert!(client.left_rooms().read().await.is_empty()); @@ -2345,7 +2350,7 @@ mod test { .with_body(test_json::LEAVE_SYNC.to_string()) .create(); - let _response = client.sync(SyncSettings::default()).await.unwrap(); + let _response = client.sync(SyncSettings::default(), None).await.unwrap(); assert!(client.joined_rooms().read().await.is_empty()); assert!(!client.left_rooms().read().await.is_empty()); @@ -2389,14 +2394,14 @@ mod test { let sync_settings = SyncSettings::new().timeout(std::time::Duration::from_millis(3000)); // gather state to save to the db, the first time through loading will be skipped - let _ = client.sync(sync_settings.clone()).await.unwrap(); + let _ = client.sync(sync_settings.clone(), None).await.unwrap(); // now syncing the client will update from the state store let config = ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap())); let client = Client::new_with_config(homeserver, config).unwrap(); client.restore_login(session.clone()).await.unwrap(); - client.sync(sync_settings).await.unwrap(); + client.sync(sync_settings, None).await.unwrap(); let base_client = &client.base_client; @@ -2457,7 +2462,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let response = client.sync(sync_settings).await.unwrap(); + let response = client.sync(sync_settings, None).await.unwrap(); assert_ne!(response.next_batch, ""); @@ -2487,7 +2492,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings).await.unwrap(); + let _response = client.sync(sync_settings, None).await.unwrap(); let mut names = vec![]; for r in client.joined_rooms().read().await.values() { From c1ffed4fc90df15d8aace772a630d40eeb99d76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jul 2020 12:44:07 +0200 Subject: [PATCH 50/83] base: Sanitize the room id for the path of the state store. This closes: #71. --- matrix_sdk_base/src/state/json_store.rs | 50 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/matrix_sdk_base/src/state/json_store.rs b/matrix_sdk_base/src/state/json_store.rs index f5074231..c33d2f02 100644 --- a/matrix_sdk_base/src/state/json_store.rs +++ b/matrix_sdk_base/src/state/json_store.rs @@ -39,6 +39,36 @@ impl JsonStore { user_path_set: AtomicBool::new(false), }) } + + /// Build a path for a file where the Room state to be stored in. + async fn build_room_path(&self, room_state: &str, room_id: &RoomId) -> PathBuf { + let mut path = self.path.read().await.clone(); + + path.push("rooms"); + path.push(room_state); + path.push(JsonStore::sanitize_room_id(room_id)); + path.set_extension("json"); + + path + } + + /// Build a path for the file where the Client state to be stored in. + async fn build_client_path(&self) -> PathBuf { + let mut path = self.path.read().await.clone(); + path.push("client"); + path.set_extension("json"); + + path + } + + /// Replace common characters that can't be used in a file name with an + /// underscore. + fn sanitize_room_id(room_id: &RoomId) -> String { + room_id.as_str().replace( + &['.', ':', '<', '>', '"', '/', '\\', '|', '?', '*'][..], + "_", + ) + } } impl fmt::Debug for JsonStore { @@ -57,8 +87,7 @@ impl StateStore for JsonStore { self.path.write().await.push(sess.user_id.localpart()) } - let mut path = self.path.read().await.clone(); - path.push("client.json"); + let path = self.build_client_path().await; let json = async_fs::read_to_string(path) .await @@ -114,8 +143,7 @@ impl StateStore for JsonStore { } async fn store_client_state(&self, state: ClientState) -> Result<()> { - let mut path = self.path.read().await.clone(); - path.push("client.json"); + let path = self.build_client_path().await; if !path.exists() { let mut dir = path.clone(); @@ -146,9 +174,7 @@ impl StateStore for JsonStore { self.path.write().await.push(room.own_user_id.localpart()) } - let mut path = self.path.read().await.clone(); - path.push("rooms"); - path.push(&format!("{}/{}.json", room_state, room.room_id)); + let path = self.build_room_path(room_state, &room.room_id).await; if !path.exists() { let mut dir = path.clone(); @@ -178,15 +204,13 @@ impl StateStore for JsonStore { return Err(Error::StateStore("path for JsonStore not set".into())); } - let mut to_del = self.path.read().await.clone(); - to_del.push("rooms"); - to_del.push(&format!("{}/{}.json", room_state, room_id)); + let path = self.build_room_path(room_state, room_id).await; - if !to_del.exists() { - return Err(Error::StateStore(format!("file {:?} not found", to_del))); + if !path.exists() { + return Err(Error::StateStore(format!("file {:?} not found", path))); } - tokio::fs::remove_file(to_del).await.map_err(Error::from) + tokio::fs::remove_file(path).await.map_err(Error::from) } } From 04bb65f43e9250eb6bba994b57c292d76feed0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jul 2020 12:46:10 +0200 Subject: [PATCH 51/83] travis: Don't test encyrption support on Windows for now. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index d35afa94..053a333f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,9 @@ jobs: os: linux - os: windows + script: + - cd matrix_sdk + - cargo build --no-default-features --features "messages" - os: osx From d75101d0421e0a0e8b538299aaede9306cbdfca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jul 2020 13:11:30 +0200 Subject: [PATCH 52/83] travis: Run the tests on Windows. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 053a333f..d495958a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: - os: windows script: - cd matrix_sdk - - cargo build --no-default-features --features "messages" + - cargo test --no-default-features --features "messages" - os: osx From 2d83c40626fbfa3ba4ab805eb7f99e2fd3b57ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jul 2020 13:45:37 +0200 Subject: [PATCH 53/83] travis: Test the base client on Windows as well. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d495958a..45128630 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,8 @@ jobs: script: - cd matrix_sdk - cargo test --no-default-features --features "messages" + - cd ../matrix_sdk_base + - cargo test --no-default-features --features "messages" - os: osx From 7c469538055ec33e74031c406d692a824dbe3cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 16 Jul 2020 14:23:36 +0200 Subject: [PATCH 54/83] travis: Don't allow failures for Windows. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 45128630..232a7e8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ addons: jobs: allow_failures: - - os: windows - os: osx name: macOS 10.15 From a5c5f5a7b10aa4e566f94fbf1790833fbfbe803c Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 16 Jul 2020 06:04:26 -0700 Subject: [PATCH 55/83] Revert "Client::sync(): expose sync filter" This reverts commit 0542e3d83d5d808c9bdb9ee3f2d389517300b6b6. --- matrix_sdk/src/client.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 6dc6402b..af962e7a 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1220,13 +1220,9 @@ impl Client { /// /// * `sync_settings` - Settings for the sync call. #[instrument] - pub async fn sync( - &self, - sync_settings: SyncSettings, - filter: Option, - ) -> Result { + pub async fn sync(&self, sync_settings: SyncSettings) -> Result { let request = sync_events::Request { - filter, + filter: None, since: sync_settings.token, full_state: sync_settings.full_state, set_presence: sync_events::SetPresence::Online, @@ -1301,7 +1297,6 @@ impl Client { pub async fn sync_forever( &self, sync_settings: SyncSettings, - filter: Option, callback: impl Fn(sync_events::Response) -> C, ) where C: Future, @@ -1314,7 +1309,7 @@ impl Client { } loop { - let response = self.sync(sync_settings.clone(), None).await; + let response = self.sync(sync_settings.clone()).await; let response = match response { Ok(r) => r, @@ -1566,7 +1561,7 @@ mod test { let room = client.get_joined_room(&room_id).await; assert!(room.is_none()); - client.sync(SyncSettings::default(), None).await.unwrap(); + client.sync(SyncSettings::default()).await.unwrap(); let room = client.get_left_room(&room_id).await; assert!(room.is_none()); @@ -1581,7 +1576,7 @@ mod test { joined_client.restore_login(session).await.unwrap(); // joined room reloaded from state store - joined_client.sync(SyncSettings::default(), None).await.unwrap(); + joined_client.sync(SyncSettings::default()).await.unwrap(); let room = joined_client.get_joined_room(&room_id).await; assert!(room.is_some()); @@ -1593,7 +1588,7 @@ mod test { .with_body(test_json::LEAVE_SYNC_EVENT.to_string()) .create(); - joined_client.sync(SyncSettings::default(), None).await.unwrap(); + joined_client.sync(SyncSettings::default()).await.unwrap(); let room = joined_client.get_joined_room(&room_id).await; assert!(room.is_none()); @@ -1625,7 +1620,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings, None).await.unwrap(); + let _response = client.sync(sync_settings).await.unwrap(); // let bc = &client.base_client; // let ignored_users = bc.ignored_users.read().await; @@ -2248,7 +2243,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings, None).await.unwrap(); + let _response = client.sync(sync_settings).await.unwrap(); let rooms_lock = &client.base_client.joined_rooms(); let rooms = rooms_lock.read().await; @@ -2284,7 +2279,7 @@ mod test { client.restore_login(session).await.unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings, None).await.unwrap(); + let _response = client.sync(sync_settings).await.unwrap(); let mut room_names = vec![]; for room in client.joined_rooms().read().await.values() { @@ -2316,7 +2311,7 @@ mod test { .with_body(test_json::INVITE_SYNC.to_string()) .create(); - let _response = client.sync(SyncSettings::default(), None).await.unwrap(); + let _response = client.sync(SyncSettings::default()).await.unwrap(); assert!(client.joined_rooms().read().await.is_empty()); assert!(client.left_rooms().read().await.is_empty()); @@ -2350,7 +2345,7 @@ mod test { .with_body(test_json::LEAVE_SYNC.to_string()) .create(); - let _response = client.sync(SyncSettings::default(), None).await.unwrap(); + let _response = client.sync(SyncSettings::default()).await.unwrap(); assert!(client.joined_rooms().read().await.is_empty()); assert!(!client.left_rooms().read().await.is_empty()); @@ -2394,14 +2389,14 @@ mod test { let sync_settings = SyncSettings::new().timeout(std::time::Duration::from_millis(3000)); // gather state to save to the db, the first time through loading will be skipped - let _ = client.sync(sync_settings.clone(), None).await.unwrap(); + let _ = client.sync(sync_settings.clone()).await.unwrap(); // now syncing the client will update from the state store let config = ClientConfig::default().state_store(Box::new(JsonStore::open(dir.path()).unwrap())); let client = Client::new_with_config(homeserver, config).unwrap(); client.restore_login(session.clone()).await.unwrap(); - client.sync(sync_settings, None).await.unwrap(); + client.sync(sync_settings).await.unwrap(); let base_client = &client.base_client; @@ -2462,7 +2457,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let response = client.sync(sync_settings, None).await.unwrap(); + let response = client.sync(sync_settings).await.unwrap(); assert_ne!(response.next_batch, ""); @@ -2492,7 +2487,7 @@ mod test { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync(sync_settings, None).await.unwrap(); + let _response = client.sync(sync_settings).await.unwrap(); let mut names = vec![]; for r in client.joined_rooms().read().await.values() { From cc4ae3db1ed1d3c4c692fb7b7c88e0bb92b59c58 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 16 Jul 2020 06:13:35 -0700 Subject: [PATCH 56/83] Client::SyncSettings: Include sync filter --- matrix_sdk/src/client.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index af962e7a..2101bea5 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -203,6 +203,7 @@ impl ClientConfig { #[derive(Debug, Default, Clone)] /// Settings for a sync call. pub struct SyncSettings { + pub(crate) filter: Option, pub(crate) timeout: Option, pub(crate) token: Option, pub(crate) full_state: bool, @@ -235,6 +236,17 @@ impl SyncSettings { self } + /// Set the sync filter. + /// It can be either the filter ID, or the definition for the filter. + /// + /// # Arguments + /// + /// * `filter` - The filter configuration that should be used for the sync call. + pub fn filter(mut self, filter: sync_events::Filter) -> Self { + self.filter = Some(filter); + self + } + /// Should the server return the full state from the start of the timeline. /// /// This does nothing if no sync token is set. @@ -1222,7 +1234,7 @@ impl Client { #[instrument] pub async fn sync(&self, sync_settings: SyncSettings) -> Result { let request = sync_events::Request { - filter: None, + filter: sync_settings.filter, since: sync_settings.token, full_state: sync_settings.full_state, set_presence: sync_events::SetPresence::Online, From c5ea4fde357d8291bd54df2e0f9054c527ee85ef Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Jul 2020 18:16:30 -0300 Subject: [PATCH 57/83] HTTP timeout --- matrix_sdk/src/client.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index af962e7a..b3f6323e 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -105,6 +105,7 @@ pub struct ClientConfig { user_agent: Option, disable_ssl_verification: bool, base_config: BaseClientConfig, + timeout: Duration } // #[cfg_attr(tarpaulin, skip)] @@ -198,6 +199,12 @@ impl ClientConfig { self.base_config = self.base_config.passphrase(passphrase); self } + + /// Set a timeout duration for all HTTP requests. The default is no timeout. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } } #[derive(Debug, Default, Clone)] @@ -298,7 +305,7 @@ impl Client { Err(_e) => panic!("Error parsing homeserver url"), }; - let http_client = reqwest::Client::builder(); + let http_client = reqwest::Client::builder().timeout(config.timeout); #[cfg(not(target_arch = "wasm32"))] let http_client = { From b0241e51a30ce95656ebf6ac2c7e55c65f63a6a5 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Jul 2020 18:40:52 -0300 Subject: [PATCH 58/83] Fixed formatting --- matrix_sdk/src/client.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index b3f6323e..f811abd9 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -105,7 +105,7 @@ pub struct ClientConfig { user_agent: Option, disable_ssl_verification: bool, base_config: BaseClientConfig, - timeout: Duration + timeout: Duration, } // #[cfg_attr(tarpaulin, skip)] @@ -200,11 +200,11 @@ impl ClientConfig { self } - /// Set a timeout duration for all HTTP requests. The default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } + /// Set a timeout duration for all HTTP requests. The default is no timeout. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } } #[derive(Debug, Default, Clone)] From 7a72949613a8f95e965ebd9c624c305df3327103 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 16 Jul 2020 15:55:55 -0700 Subject: [PATCH 59/83] Client::sync_forever(): Add filter in next iteration. --- matrix_sdk/src/client.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 2101bea5..831547c8 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1314,6 +1314,7 @@ impl Client { C: Future, { let mut sync_settings = sync_settings; + let filter = sync_settings.filter.clone(); let mut last_sync_time: Option = None; if sync_settings.token.is_none() { @@ -1373,6 +1374,9 @@ impl Client { .await .expect("No sync token found after initial sync"), ); + if let Some(f) = filter.as_ref() { + sync_settings = sync_settings.filter(f.clone()); + } } } From 2f99d0de59e44b1fcd0bba4850fdd4d543a9c4f2 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Jul 2020 20:06:26 -0300 Subject: [PATCH 60/83] Bugfix --- matrix_sdk/src/client.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index f811abd9..16e4325c 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -105,7 +105,7 @@ pub struct ClientConfig { user_agent: Option, disable_ssl_verification: bool, base_config: BaseClientConfig, - timeout: Duration, + timeout: Option, } // #[cfg_attr(tarpaulin, skip)] @@ -202,7 +202,7 @@ impl ClientConfig { /// Set a timeout duration for all HTTP requests. The default is no timeout. pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; + self.timeout = Some(timeout); self } } @@ -305,7 +305,12 @@ impl Client { Err(_e) => panic!("Error parsing homeserver url"), }; - let http_client = reqwest::Client::builder().timeout(config.timeout); + let http_client = reqwest::Client::builder(); + + let http_client = match (config.timeout) { + Some(x) => http_client.timeout(x), + None => http_client, + }; #[cfg(not(target_arch = "wasm32"))] let http_client = { From 44dfbd2fa6bbb6507496489ed2a6724f2f1a95b1 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Jul 2020 20:21:34 -0300 Subject: [PATCH 61/83] Fix --- matrix_sdk/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 16e4325c..fffed1b4 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -307,7 +307,7 @@ impl Client { let http_client = reqwest::Client::builder(); - let http_client = match (config.timeout) { + let http_client = match config.timeout { Some(x) => http_client.timeout(x), None => http_client, }; From f2163164bfe3eb1952d1b9ae2b534266c1d20e71 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Jul 2020 20:53:09 -0300 Subject: [PATCH 62/83] Wasm fix --- matrix_sdk/src/client.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index fffed1b4..361bc3a1 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -307,13 +307,13 @@ impl Client { let http_client = reqwest::Client::builder(); - let http_client = match config.timeout { - Some(x) => http_client.timeout(x), - None => http_client, - }; - #[cfg(not(target_arch = "wasm32"))] let http_client = { + let http_client = match config.timeout { + Some(x) => http_client.timeout(x), + None => http_client, + }; + let http_client = if config.disable_ssl_verification { http_client.danger_accept_invalid_certs(true) } else { From d273786d83fae612a12da334a0b1e7774d911b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 17 Jul 2020 10:01:22 +0200 Subject: [PATCH 63/83] matrix-sdk: Bump our dependencies. --- matrix_sdk/Cargo.toml | 6 +++--- matrix_sdk_base/Cargo.toml | 4 ++-- matrix_sdk_common/Cargo.toml | 2 +- matrix_sdk_common_macros/Cargo.toml | 2 +- matrix_sdk_crypto/Cargo.toml | 2 +- matrix_sdk_test/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index 505f1d73..b1438d11 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -21,7 +21,7 @@ http = "0.2.1" reqwest = "0.10.6" serde_json = "1.0.56" thiserror = "1.0.20" -tracing = "0.1.15" +tracing = "0.1.16" url = "2.1.1" matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_macros" } @@ -37,8 +37,8 @@ version = "0.2.4" default-features = false features = ["std", "std-future"] -[target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures-timer] -version = "3.0.2" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +futures-timer = "3.0.2" [target.'cfg(target_arch = "wasm32")'.dependencies.futures-timer] version = "3.0.2" diff --git a/matrix_sdk_base/Cargo.toml b/matrix_sdk_base/Cargo.toml index bbb9a264..5c16e448 100644 --- a/matrix_sdk_base/Cargo.toml +++ b/matrix_sdk_base/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.36" serde = "1.0.114" serde_json = "1.0.56" zeroize = "1.1.0" -tracing = "0.1.14" +tracing = "0.1.16" matrix-sdk-common-macros = { version = "0.1.0", path = "../matrix_sdk_common_macros" } matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } @@ -46,4 +46,4 @@ mockito = "0.26.0" tokio = { version = "0.2.21", features = ["rt-threaded", "macros"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = "0.3.14" +wasm-bindgen-test = "0.3.15" diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 2bdc5f65..9700f812 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk" version = "0.1.0" [dependencies] -instant = { version = "0.1.4", features = ["wasm-bindgen", "now"] } +instant = { version = "0.1.6", features = ["wasm-bindgen", "now"] } js_int = "0.1.8" [dependencies.ruma] diff --git a/matrix_sdk_common_macros/Cargo.toml b/matrix_sdk_common_macros/Cargo.toml index 2b0c6564..959d5805 100644 --- a/matrix_sdk_common_macros/Cargo.toml +++ b/matrix_sdk_common_macros/Cargo.toml @@ -14,5 +14,5 @@ version = "0.1.0" proc-macro = true [dependencies] -syn = "1.0.33" +syn = "1.0.34" quote = "1.0.7" diff --git a/matrix_sdk_crypto/Cargo.toml b/matrix_sdk_crypto/Cargo.toml index a5e9833a..ea1fc330 100644 --- a/matrix_sdk_crypto/Cargo.toml +++ b/matrix_sdk_crypto/Cargo.toml @@ -29,7 +29,7 @@ url = "2.1.1" # Misc dependencies thiserror = "1.0.20" -tracing = "0.1.15" +tracing = "0.1.16" atomic = "0.4.6" dashmap = "3.11.7" diff --git a/matrix_sdk_test/Cargo.toml b/matrix_sdk_test/Cargo.toml index 07a0c8a6..17ca6f84 100644 --- a/matrix_sdk_test/Cargo.toml +++ b/matrix_sdk_test/Cargo.toml @@ -16,4 +16,4 @@ http = "0.2.1" matrix-sdk-common = { version = "0.1.0", path = "../matrix_sdk_common" } matrix-sdk-test-macros = { version = "0.1.0", path = "../matrix_sdk_test_macros" } lazy_static = "1.4.0" -serde = "1.0.111" +serde = "1.0.114" From 2e8fc3e23203a42bd2eabe770f56c08b3353ee74 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 11 Jul 2020 21:12:40 -0400 Subject: [PATCH 64/83] matrix-sdk-base: Integrate redacted events into message queue Redact message events according to spec and ruma types. Remove content using events redact() method and insert the redacting event into the event being redacted. --- matrix_sdk/src/lib.rs | 2 + matrix_sdk_base/src/client.rs | 28 ++++-- matrix_sdk_base/src/event_emitter/mod.rs | 2 +- matrix_sdk_base/src/lib.rs | 11 ++- matrix_sdk_base/src/models/message.rs | 75 +++++++++++----- matrix_sdk_base/src/models/mod.rs | 3 + matrix_sdk_base/src/models/room.rs | 109 +++++++++++++++++------ matrix_sdk_common/Cargo.toml | 2 +- 8 files changed, 170 insertions(+), 62 deletions(-) diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 6bdfd0dc..809b8e20 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -40,6 +40,8 @@ pub use matrix_sdk_base::Error as BaseError; #[cfg(not(target_arch = "wasm32"))] pub use matrix_sdk_base::JsonStore; pub use matrix_sdk_base::{CustomOrRawEvent, EventEmitter, Room, Session, SyncRoom}; +#[cfg(feature = "messages")] +pub use matrix_sdk_base::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; pub use matrix_sdk_base::{RoomState, StateStore}; pub use matrix_sdk_common::*; pub use reqwest::header::InvalidHeaderValue; diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 1c38b90c..660bfe1f 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -1511,6 +1511,8 @@ impl BaseClient { } _ => {} }, + AnyRoomEventStub::RedactedState(_event) => {} + AnyRoomEventStub::RedactedMessage(_event) => {} } } @@ -1836,7 +1838,7 @@ impl BaseClient { #[cfg(test)] mod test { - use crate::identifiers::{RoomId, UserId}; + use crate::identifiers::{EventId, RoomId, UserId}; use crate::{BaseClient, BaseClientConfig, Session}; use matrix_sdk_common::events::{AnyRoomEventStub, EventJson}; use matrix_sdk_common_macros::async_trait; @@ -2402,12 +2404,17 @@ mod test { // check that the message has actually been redacted for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::events::AnyMessageEventContent::RoomRedaction(content) = - &queue.msgs[0].content + if let crate::models::FullOrRedactedEvent::Redacted( + crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + ) = &queue.msgs[0].deref() { - assert_eq!(content.reason, Some("😀".to_string())); + // this is the id from the message event in the sync response + assert_eq!( + event.event_id, + EventId::try_from("$152037280074GZeOm:localhost").unwrap() + ) } else { - panic!("[pre store sync] message event in message queue should be redacted") + panic!("message event in message queue should be redacted") } } @@ -2425,10 +2432,15 @@ mod test { // properly for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::events::AnyMessageEventContent::RoomRedaction(content) = - &queue.msgs[0].content + if let crate::models::FullOrRedactedEvent::Redacted( + crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + ) = &queue.msgs[0].deref() { - assert_eq!(content.reason, Some("😀".to_string())); + // this is the id from the message event in the sync response + assert_eq!( + event.event_id, + EventId::try_from("$152037280074GZeOm:localhost").unwrap() + ) } else { panic!("[post store sync] message event in message queue should be redacted") } diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index e0722eb1..bb7c6420 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -581,7 +581,7 @@ mod test { "unrecognized event", "redaction", "unrecognized event", - "unrecognized event", + // "unrecognized event", this is actually a redacted "m.room.messages" event "receipt event", "typing event" ], diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 1ca09880..ab7f1e77 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -47,11 +47,16 @@ mod state; pub use client::{BaseClient, BaseClientConfig, RoomState, RoomStateType}; pub use event_emitter::{CustomOrRawEvent, EventEmitter, SyncRoom}; +pub use models::Room; +pub use state::{AllRooms, ClientState}; + #[cfg(feature = "encryption")] pub use matrix_sdk_crypto::{Device, TrustState}; -pub use models::Room; -pub use state::AllRooms; -pub use state::ClientState; + +#[cfg(feature = "messages")] +#[cfg_attr(docsrs, doc(cfg(feature = "messages")))] +pub use models::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; + #[cfg(not(target_arch = "wasm32"))] pub use state::JsonStore; pub use state::StateStore; diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index 4bb0aae2..401b258a 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -3,17 +3,50 @@ //! The `Room` struct optionally holds a `MessageQueue` if the "messages" //! feature is enabled. -use std::cmp::Ordering; -use std::ops::{Deref, DerefMut}; -use std::vec::IntoIter; +use std::{ + cmp::Ordering, + ops::{Deref, DerefMut}, + time::SystemTime, + vec::IntoIter, +}; -use crate::events::{AnyMessageEventContent, AnyMessageEventStub, MessageEventStub}; +use matrix_sdk_common::identifiers::EventId; +use serde::{de, ser, Deserialize, Serialize}; -use serde::{de, ser, Serialize}; +use crate::events::{AnyMessageEventStub, AnyRedactedMessageEventStub}; + +/// Represents either a redacted event or a non-redacted event. +/// +/// Note: ruma may create types that solve this. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum FullOrRedactedEvent { + /// A non-redacted event. + Full(AnyMessageEventStub), + /// An event that has been redacted. + Redacted(AnyRedactedMessageEventStub), +} + +impl FullOrRedactedEvent { + /// Access the underlying events `event_id`. + pub fn event_id(&self) -> &EventId { + match self { + Self::Full(e) => e.event_id(), + Self::Redacted(e) => e.event_id(), + } + } + + /// Access the underlying events `origin_server_ts`. + pub fn origin_server_ts(&self) -> &SystemTime { + match self { + Self::Full(e) => e.origin_server_ts(), + Self::Redacted(e) => e.origin_server_ts(), + } + } +} const MESSAGE_QUEUE_CAP: usize = 35; -pub type SyncMessageEvent = MessageEventStub; +pub type SyncMessageEvent = FullOrRedactedEvent; /// A queue that holds the 35 most recent messages received from the server. #[derive(Clone, Debug, Default)] @@ -29,18 +62,6 @@ pub struct MessageQueue { #[derive(Clone, Debug, Serialize)] pub struct MessageWrapper(pub SyncMessageEvent); -impl MessageWrapper { - pub fn clone_into_any_content(event: &AnyMessageEventStub) -> SyncMessageEvent { - MessageEventStub { - content: event.content(), - sender: event.sender().clone(), - origin_server_ts: *event.origin_server_ts(), - event_id: event.event_id().clone(), - unsigned: event.unsigned().clone(), - } - } -} - impl Deref for MessageWrapper { type Target = SyncMessageEvent; @@ -57,7 +78,7 @@ impl DerefMut for MessageWrapper { impl PartialEq for MessageWrapper { fn eq(&self, other: &MessageWrapper) -> bool { - self.0.event_id == other.0.event_id + self.0.event_id() == other.0.event_id() } } @@ -65,7 +86,7 @@ impl Eq for MessageWrapper {} impl PartialOrd for MessageWrapper { fn partial_cmp(&self, other: &MessageWrapper) -> Option { - Some(self.0.origin_server_ts.cmp(&other.0.origin_server_ts)) + Some(self.0.origin_server_ts().cmp(&other.0.origin_server_ts())) } } @@ -82,7 +103,7 @@ impl PartialEq for MessageQueue { .msgs .iter() .zip(other.msgs.iter()) - .all(|(msg_a, msg_b)| msg_a.event_id == msg_b.event_id) + .all(|(msg_a, msg_b)| msg_a.event_id() == msg_b.event_id()) } } @@ -100,7 +121,7 @@ impl MessageQueue { pub fn push(&mut self, msg: SyncMessageEvent) -> bool { // only push new messages into the queue if let Some(latest) = self.msgs.last() { - if msg.origin_server_ts < latest.origin_server_ts && self.msgs.len() >= 10 { + if msg.origin_server_ts() < latest.origin_server_ts() && self.msgs.len() >= 10 { return false; } } @@ -120,10 +141,12 @@ impl MessageQueue { true } + /// Iterate over the messages in the queue. pub fn iter(&self) -> impl Iterator { self.msgs.iter() } + /// Iterate over each message mutably. pub fn iter_mut(&mut self) -> impl Iterator { self.msgs.iter_mut() } @@ -204,7 +227,9 @@ mod test { let mut room = Room::new(&id, &user); let json: &serde_json::Value = &test_json::MESSAGE_TEXT; - let msg = serde_json::from_value::(json.clone()).unwrap(); + let msg = FullOrRedactedEvent::Full( + serde_json::from_value::(json.clone()).unwrap(), + ); let mut msgs = MessageQueue::new(); msgs.push(msg.clone()); @@ -249,7 +274,9 @@ mod test { let mut room = Room::new(&id, &user); let json: &serde_json::Value = &test_json::MESSAGE_TEXT; - let msg = serde_json::from_value::(json.clone()).unwrap(); + let msg = FullOrRedactedEvent::Full( + serde_json::from_value::(json.clone()).unwrap(), + ); let mut msgs = MessageQueue::new(); msgs.push(msg.clone()); diff --git a/matrix_sdk_base/src/models/mod.rs b/matrix_sdk_base/src/models/mod.rs index 211e5b5d..4cf49a20 100644 --- a/matrix_sdk_base/src/models/mod.rs +++ b/matrix_sdk_base/src/models/mod.rs @@ -4,5 +4,8 @@ mod message; mod room; mod room_member; +#[cfg(feature = "messages")] +#[cfg_attr(docsrs, doc(cfg(feature = "messages")))] +pub use message::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; pub use room::{Room, RoomName}; pub use room_member::RoomMember; diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index ae1d9c88..3be5980d 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -20,22 +20,21 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, error, trace}; #[cfg(feature = "messages")] -use super::message::{MessageQueue, MessageWrapper}; +use super::message::{FullOrRedactedEvent, MessageQueue}; use super::RoomMember; use crate::api::r0::sync::sync_events::{RoomSummary, UnreadNotificationsCount}; -use crate::events::presence::{PresenceEvent, PresenceEventContent}; -use crate::events::room::{ - aliases::AliasesEventContent, - canonical_alias::CanonicalAliasEventContent, - encryption::EncryptionEventContent, - member::{MemberEventContent, MembershipChange, MembershipState}, - name::NameEventContent, - power_levels::{NotificationPowerLevels, PowerLevelsEventContent}, - tombstone::TombstoneEventContent, -}; - use crate::events::{ + presence::PresenceEvent, + room::{ + aliases::AliasesEventContent, + canonical_alias::CanonicalAliasEventContent, + encryption::EncryptionEventContent, + member::{MemberEventContent, MembershipChange}, + name::NameEventContent, + power_levels::{NotificationPowerLevels, PowerLevelsEventContent}, + tombstone::TombstoneEventContent, + }, Algorithm, AnyRoomEventStub, AnyStateEventStub, AnyStrippedStateEventStub, EventType, StateEventStub, StrippedStateEventStub, }; @@ -43,7 +42,7 @@ use crate::events::{ #[cfg(feature = "messages")] use crate::events::{ room::redaction::{RedactionEvent, RedactionEventStub}, - AnyMessageEventContent, AnyMessageEventStub, EventJson, + AnyMessageEventStub, EventJson, }; use crate::identifiers::{RoomAliasId, RoomId, UserId}; @@ -674,8 +673,7 @@ impl Room { #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] pub fn handle_message(&mut self, event: &AnyMessageEventStub) -> bool { - let message = MessageWrapper::clone_into_any_content(event); - self.messages.push(message) + self.messages.push(FullOrRedactedEvent::Full(event.clone())) } /// Handle a room.redaction event and update the `MessageQueue`. @@ -686,17 +684,72 @@ impl Room { /// field. #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] - pub fn handle_redaction(&mut self, event: &RedactionEventStub) -> bool { + pub fn handle_redaction(&mut self, redacted_event: &RedactionEventStub) -> bool { + use matrix_sdk_common::events::{ + AnyRedactedMessageEventStub, MessageEventStub, RedactedMessageEventStub, + }; + use std::ops::DerefMut; + if let Some(msg) = self .messages .iter_mut() - .find(|msg| event.redacts == msg.event_id) + .find(|msg| &redacted_event.redacts == msg.event_id()) { - msg.content = AnyMessageEventContent::RoomRedaction(event.content.clone()); - - let redaction = - redaction_event_from_redaction_stub(event.clone(), self.room_id.clone()); - msg.unsigned.redacted_because = Some(EventJson::from(redaction)); + match msg.deref_mut() { + FullOrRedactedEvent::Full(event) => match event { + AnyMessageEventStub::RoomMessage(event) => { + let MessageEventStub { + content, + event_id, + sender, + origin_server_ts, + mut unsigned, + } = event.clone(); + unsigned.redacted_because = + Some(EventJson::from(redaction_event_from_redaction_stub( + redacted_event.clone(), + self.room_id.clone(), + ))); + let redacted = content.redact(); + msg.0 = FullOrRedactedEvent::Redacted( + AnyRedactedMessageEventStub::RoomMessage(RedactedMessageEventStub { + content: redacted, + event_id, + origin_server_ts, + sender, + unsigned, + }), + ) + } + AnyMessageEventStub::Sticker(event) => { + let MessageEventStub { + content, + event_id, + sender, + origin_server_ts, + mut unsigned, + } = event.clone(); + unsigned.redacted_because = + Some(EventJson::from(redaction_event_from_redaction_stub( + redacted_event.clone(), + self.room_id.clone(), + ))); + let redacted = content.redact(); + msg.0 = FullOrRedactedEvent::Redacted(AnyRedactedMessageEventStub::Sticker( + RedactedMessageEventStub { + content: redacted, + event_id, + origin_server_ts, + sender, + unsigned, + }, + )) + } + // TODO handle the rest of the message events + _ => {} + }, + FullOrRedactedEvent::Redacted(_) => return false, + } true } else { false @@ -815,6 +868,7 @@ impl Room { AnyMessageEventStub::RoomRedaction(event) => self.handle_redaction(event), _ => false, }, + AnyRoomEventStub::RedactedMessage(_) | AnyRoomEventStub::RedactedState(_) => false, } } @@ -1661,10 +1715,15 @@ mod test { for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::events::AnyMessageEventContent::RoomRedaction(content) = - &queue.msgs[0].content + if let crate::models::message::FullOrRedactedEvent::Redacted( + crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + ) = &queue.msgs[0].deref() { - assert_eq!(content.reason, Some("😀".to_string())); + // this is the id from the message event in the sync response + assert_eq!( + event.event_id, + EventId::try_from("$152037280074GZeOm:localhost").unwrap() + ) } else { panic!("message event in message queue should be redacted") } diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 9700f812..b1ae4696 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -17,7 +17,7 @@ js_int = "0.1.8" [dependencies.ruma] git = "https://github.com/ruma/ruma" features = ["client-api"] -rev = "c19bcaab" +rev = "5e428ac" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] uuid = { version = "0.8.1", features = ["v4"] } From 71f2a042c2f7cee4393c222e58e8aeb8710feb23 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 18 Jul 2020 08:37:43 -0400 Subject: [PATCH 65/83] Rename Stub -> Sync for all ruma events --- .pre-commit-config.yaml | 24 +-- matrix_sdk/examples/autojoin.rs | 4 +- matrix_sdk/examples/command_bot.rs | 6 +- matrix_sdk/examples/login.rs | 6 +- .../examples/wasm_command_bot/src/lib.rs | 8 +- matrix_sdk_base/src/client.rs | 156 +++++++++--------- matrix_sdk_base/src/event_emitter/mod.rs | 119 +++++++------ matrix_sdk_base/src/models/message.rs | 10 +- matrix_sdk_base/src/models/room.rs | 125 +++++++------- matrix_sdk_base/src/models/room_member.rs | 19 ++- matrix_sdk_common/Cargo.toml | 2 +- matrix_sdk_crypto/src/machine.rs | 16 +- matrix_sdk_test/src/lib.rs | 28 ++-- 13 files changed, 261 insertions(+), 262 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index afdeace7..6ca778bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,16 +14,16 @@ repos: types: [file, rust] entry: cargo fmt -- --check - - id: clippy - name: clippy - language: system - types: [file, rust] - entry: cargo clippy --all-targets --all - pass_filenames: false + # - id: clippy + # name: clippy + # language: system + # types: [file, rust] + # entry: cargo clippy --all-targets --all + # pass_filenames: false - - id: test - name: test - language: system - files: '\.rs$' - entry: cargo test --lib - pass_filenames: false + # - id: test + # name: test + # language: system + # files: '\.rs$' + # entry: cargo test --lib + # pass_filenames: false diff --git a/matrix_sdk/examples/autojoin.rs b/matrix_sdk/examples/autojoin.rs index ed73a48e..d8813ae4 100644 --- a/matrix_sdk/examples/autojoin.rs +++ b/matrix_sdk/examples/autojoin.rs @@ -2,7 +2,7 @@ use std::{env, process::exit}; use matrix_sdk::{ self, - events::{room::member::MemberEventContent, StrippedStateEventStub}, + events::{room::member::MemberEventContent, StrippedStateEvent}, Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, }; use matrix_sdk_common_macros::async_trait; @@ -23,7 +23,7 @@ impl EventEmitter for AutoJoinBot { async fn on_stripped_state_member( &self, room: SyncRoom, - room_member: &StrippedStateEventStub, + room_member: &StrippedStateEvent, _: Option, ) { if room_member.state_key != self.client.user_id().await.unwrap() { diff --git a/matrix_sdk/examples/command_bot.rs b/matrix_sdk/examples/command_bot.rs index 2a84c1d6..9e186c01 100644 --- a/matrix_sdk/examples/command_bot.rs +++ b/matrix_sdk/examples/command_bot.rs @@ -4,7 +4,7 @@ use matrix_sdk::{ self, events::{ room::message::{MessageEventContent, TextMessageEventContent}, - MessageEventStub, + SyncMessageEvent, }, Client, ClientConfig, EventEmitter, JsonStore, SyncRoom, SyncSettings, }; @@ -25,9 +25,9 @@ impl CommandBot { #[async_trait] impl EventEmitter for CommandBot { - async fn on_room_message(&self, room: SyncRoom, event: &MessageEventStub) { + async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { if let SyncRoom::Joined(room) = room { - let msg_body = if let MessageEventStub { + let msg_body = if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. } = event diff --git a/matrix_sdk/examples/login.rs b/matrix_sdk/examples/login.rs index 73559a6d..ca3276f8 100644 --- a/matrix_sdk/examples/login.rs +++ b/matrix_sdk/examples/login.rs @@ -5,7 +5,7 @@ use matrix_sdk::{ self, events::{ room::message::{MessageEventContent, TextMessageEventContent}, - MessageEventStub, + SyncMessageEvent, }, Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, }; @@ -15,9 +15,9 @@ struct EventCallback; #[async_trait] impl EventEmitter for EventCallback { - async fn on_room_message(&self, room: SyncRoom, event: &MessageEventStub) { + async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { if let SyncRoom::Joined(room) = room { - if let MessageEventStub { + if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, .. diff --git a/matrix_sdk/examples/wasm_command_bot/src/lib.rs b/matrix_sdk/examples/wasm_command_bot/src/lib.rs index fa944b3c..0167d5c3 100644 --- a/matrix_sdk/examples/wasm_command_bot/src/lib.rs +++ b/matrix_sdk/examples/wasm_command_bot/src/lib.rs @@ -2,7 +2,7 @@ use matrix_sdk::{ api::r0::sync::sync_events::Response as SyncResponse, events::{ room::message::{MessageEventContent, TextMessageEventContent}, - AnyMessageEventStub, AnyRoomEventStub, MessageEventStub, + AnySyncMessageEvent, AnySyncRoomEvent, SyncMessageEvent, }, identifiers::RoomId, Client, ClientConfig, SyncSettings, @@ -17,9 +17,9 @@ impl WasmBot { async fn on_room_message( &self, room_id: &RoomId, - event: MessageEventStub, + event: SyncMessageEvent, ) { - let msg_body = if let MessageEventStub { + let msg_body = if let SyncMessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. } = event @@ -45,7 +45,7 @@ impl WasmBot { for (room_id, room) in response.rooms.join { for event in room.timeline.events { if let Ok(event) = event.deserialize() { - if let AnyRoomEventStub::Message(AnyMessageEventStub::RoomMessage(ev)) = event { + if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(ev)) = event { self.on_room_message(&room_id, ev).await } } diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 660bfe1f..3eb9e260 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -38,8 +38,8 @@ use crate::session::Session; use crate::state::{AllRooms, ClientState, StateStore}; use crate::EventEmitter; use matrix_sdk_common::events::{ - AnyBasicEvent, AnyEphemeralRoomEventStub, AnyMessageEventStub, AnyRoomEventStub, - AnyStateEventStub, AnyStrippedStateEventStub, EventJson, + AnyBasicEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncMessageEvent, + AnySyncRoomEvent, AnySyncStateEvent, EventJson, }; #[cfg(feature = "encryption")] @@ -94,8 +94,8 @@ pub struct AdditionalUnsignedData { /// [synapse-bug]: /// [discussion]: fn hoist_room_event_prev_content( - event: &EventJson, -) -> Option> { + event: &EventJson, +) -> Option> { let prev_content = serde_json::from_str::(event.json().get()) .map(|more_unsigned| more_unsigned.unsigned) .map(|additional| additional.prev_content) @@ -105,7 +105,7 @@ fn hoist_room_event_prev_content( let mut ev = event.deserialize().ok()?; match &mut ev { - AnyRoomEventStub::State(AnyStateEventStub::RoomMember(ref mut member)) + AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(ref mut member)) if member.prev_content.is_none() => { if let Ok(prev) = prev_content.deserialize() { @@ -122,8 +122,8 @@ fn hoist_room_event_prev_content( /// /// See comment of `hoist_room_event_prev_content`. fn hoist_state_event_prev_content( - event: &EventJson, -) -> Option> { + event: &EventJson, +) -> Option> { let prev_content = serde_json::from_str::(event.json().get()) .map(|more_unsigned| more_unsigned.unsigned) .map(|additional| additional.prev_content) @@ -132,7 +132,7 @@ fn hoist_state_event_prev_content( let mut ev = event.deserialize().ok()?; match &mut ev { - AnyStateEventStub::RoomMember(ref mut member) if member.prev_content.is_none() => { + AnySyncStateEvent::RoomMember(ref mut member) if member.prev_content.is_none() => { member.prev_content = Some(prev_content.deserialize().ok()?); Some(EventJson::from(ev)) } @@ -141,7 +141,7 @@ fn hoist_state_event_prev_content( } fn stripped_deserialize_prev_content( - event: &EventJson, + event: &EventJson, ) -> Option { serde_json::from_str::(event.json().get()) .map(|more_unsigned| more_unsigned.unsigned) @@ -488,7 +488,7 @@ impl BaseClient { *olm = Some( OlmMachine::new_with_store( session.user_id.to_owned(), - session.device_id.to_owned(), + session.device_id.as_str().into(), store, ) .await @@ -713,14 +713,14 @@ impl BaseClient { pub async fn receive_joined_timeline_event( &self, room_id: &RoomId, - event: &mut EventJson, + event: &mut EventJson, ) -> Result { match event.deserialize() { #[allow(unused_mut)] Ok(mut e) => { #[cfg(feature = "encryption")] { - if let AnyRoomEventStub::Message(AnyMessageEventStub::RoomEncrypted( + if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomEncrypted( ref mut encrypted_event, )) = e { @@ -742,7 +742,7 @@ impl BaseClient { let room_lock = self.get_or_create_joined_room(&room_id).await?; let mut room = room_lock.write().await; - if let AnyRoomEventStub::State(AnyStateEventStub::RoomMember(mem_event)) = &mut e { + if let AnySyncRoomEvent::State(AnySyncStateEvent::RoomMember(mem_event)) = &mut e { let (changed, _) = room.handle_membership(mem_event, false); // The memberlist of the room changed, invalidate the group session @@ -774,12 +774,12 @@ impl BaseClient { pub async fn receive_joined_state_event( &self, room_id: &RoomId, - event: &AnyStateEventStub, + event: &AnySyncStateEvent, ) -> Result { let room_lock = self.get_or_create_joined_room(room_id).await?; let mut room = room_lock.write().await; - if let AnyStateEventStub::RoomMember(e) = event { + if let AnySyncStateEvent::RoomMember(e) = event { let (changed, _) = room.handle_membership(e, true); // The memberlist of the room changed, invalidate the group session @@ -808,7 +808,7 @@ impl BaseClient { pub async fn receive_invite_state_event( &self, room_id: &RoomId, - event: &AnyStrippedStateEventStub, + event: &AnyStrippedStateEvent, ) -> Result { let room_lock = self.get_or_create_invited_room(room_id).await?; let mut room = room_lock.write().await; @@ -828,7 +828,7 @@ impl BaseClient { pub async fn receive_left_timeline_event( &self, room_id: &RoomId, - event: &EventJson, + event: &EventJson, ) -> Result { match event.deserialize() { Ok(e) => { @@ -853,7 +853,7 @@ impl BaseClient { pub async fn receive_left_state_event( &self, room_id: &RoomId, - event: &AnyStateEventStub, + event: &AnySyncStateEvent, ) -> Result { let room_lock = self.get_or_create_left_room(room_id).await?; let mut room = room_lock.write().await; @@ -906,11 +906,11 @@ impl BaseClient { /// * `room_id` - The unique id of the room the event belongs to. /// /// * `event` - The presence event for a specified room member. - pub async fn receive_ephemeral_event(&self, event: &AnyEphemeralRoomEventStub) -> bool { + pub async fn receive_ephemeral_event(&self, event: &AnySyncEphemeralRoomEvent) -> bool { match event { - AnyEphemeralRoomEventStub::FullyRead(_) => {} - AnyEphemeralRoomEventStub::Receipt(_) => {} - AnyEphemeralRoomEventStub::Typing(_) => {} + AnySyncEphemeralRoomEvent::FullyRead(_) => {} + AnySyncEphemeralRoomEvent::Receipt(_) => {} + AnySyncEphemeralRoomEvent::Typing(_) => {} _ => {} }; false @@ -1197,7 +1197,7 @@ impl BaseClient { if let Ok(mut e) = event.deserialize() { // if the event is a m.room.member event the server will sometimes // send the `prev_content` field as part of the unsigned field. - if let AnyStrippedStateEventStub::RoomMember(_) = &mut e { + if let AnyStrippedStateEvent::RoomMember(_) = &mut e { if let Some(raw_content) = stripped_deserialize_prev_content(event) { let prev_content = match raw_content.prev_content { Some(json) => json.deserialize().ok(), @@ -1280,7 +1280,7 @@ impl BaseClient { pub async fn get_missing_sessions( &self, users: impl Iterator, - ) -> Result>> { + ) -> Result, KeyAlgorithm>>> { let mut olm = self.olm.lock().await; match &mut *olm { @@ -1437,7 +1437,7 @@ impl BaseClient { pub(crate) async fn emit_timeline_event( &self, room_id: &RoomId, - event: &AnyRoomEventStub, + event: &AnySyncRoomEvent, room_state: RoomStateType, ) { let lock = self.event_emitter.read().await; @@ -1472,54 +1472,54 @@ impl BaseClient { }; match event { - AnyRoomEventStub::State(event) => match event { - AnyStateEventStub::RoomMember(e) => event_emitter.on_room_member(room, e).await, - AnyStateEventStub::RoomName(e) => event_emitter.on_room_name(room, e).await, - AnyStateEventStub::RoomCanonicalAlias(e) => { + AnySyncRoomEvent::State(event) => match event { + AnySyncStateEvent::RoomMember(e) => event_emitter.on_room_member(room, e).await, + AnySyncStateEvent::RoomName(e) => event_emitter.on_room_name(room, e).await, + AnySyncStateEvent::RoomCanonicalAlias(e) => { event_emitter.on_room_canonical_alias(room, e).await } - AnyStateEventStub::RoomAliases(e) => event_emitter.on_room_aliases(room, e).await, - AnyStateEventStub::RoomAvatar(e) => event_emitter.on_room_avatar(room, e).await, - AnyStateEventStub::RoomPowerLevels(e) => { + AnySyncStateEvent::RoomAliases(e) => event_emitter.on_room_aliases(room, e).await, + AnySyncStateEvent::RoomAvatar(e) => event_emitter.on_room_avatar(room, e).await, + AnySyncStateEvent::RoomPowerLevels(e) => { event_emitter.on_room_power_levels(room, e).await } - AnyStateEventStub::RoomTombstone(e) => { + AnySyncStateEvent::RoomTombstone(e) => { event_emitter.on_room_tombstone(room, e).await } - AnyStateEventStub::RoomJoinRules(e) => { + AnySyncStateEvent::RoomJoinRules(e) => { event_emitter.on_room_join_rules(room, e).await } - AnyStateEventStub::Custom(e) => { + AnySyncStateEvent::Custom(e) => { event_emitter .on_unrecognized_event(room, &CustomOrRawEvent::State(e)) .await } _ => {} }, - AnyRoomEventStub::Message(event) => match event { - AnyMessageEventStub::RoomMessage(e) => event_emitter.on_room_message(room, e).await, - AnyMessageEventStub::RoomMessageFeedback(e) => { + AnySyncRoomEvent::Message(event) => match event { + AnySyncMessageEvent::RoomMessage(e) => event_emitter.on_room_message(room, e).await, + AnySyncMessageEvent::RoomMessageFeedback(e) => { event_emitter.on_room_message_feedback(room, e).await } - AnyMessageEventStub::RoomRedaction(e) => { + AnySyncMessageEvent::RoomRedaction(e) => { event_emitter.on_room_redaction(room, e).await } - AnyMessageEventStub::Custom(e) => { + AnySyncMessageEvent::Custom(e) => { event_emitter .on_unrecognized_event(room, &CustomOrRawEvent::Message(e)) .await } _ => {} }, - AnyRoomEventStub::RedactedState(_event) => {} - AnyRoomEventStub::RedactedMessage(_event) => {} + AnySyncRoomEvent::RedactedState(_event) => {} + AnySyncRoomEvent::RedactedMessage(_event) => {} } } pub(crate) async fn emit_state_event( &self, room_id: &RoomId, - event: &AnyStateEventStub, + event: &AnySyncStateEvent, room_state: RoomStateType, ) { let lock = self.event_emitter.read().await; @@ -1554,32 +1554,32 @@ impl BaseClient { }; match event { - AnyStateEventStub::RoomMember(member) => { + AnySyncStateEvent::RoomMember(member) => { event_emitter.on_state_member(room, &member).await } - AnyStateEventStub::RoomName(name) => event_emitter.on_state_name(room, &name).await, - AnyStateEventStub::RoomCanonicalAlias(canonical) => { + AnySyncStateEvent::RoomName(name) => event_emitter.on_state_name(room, &name).await, + AnySyncStateEvent::RoomCanonicalAlias(canonical) => { event_emitter .on_state_canonical_alias(room, &canonical) .await } - AnyStateEventStub::RoomAliases(aliases) => { + AnySyncStateEvent::RoomAliases(aliases) => { event_emitter.on_state_aliases(room, &aliases).await } - AnyStateEventStub::RoomAvatar(avatar) => { + AnySyncStateEvent::RoomAvatar(avatar) => { event_emitter.on_state_avatar(room, &avatar).await } - AnyStateEventStub::RoomPowerLevels(power) => { + AnySyncStateEvent::RoomPowerLevels(power) => { event_emitter.on_state_power_levels(room, &power).await } - AnyStateEventStub::RoomJoinRules(rules) => { + AnySyncStateEvent::RoomJoinRules(rules) => { event_emitter.on_state_join_rules(room, &rules).await } - AnyStateEventStub::RoomTombstone(tomb) => { + AnySyncStateEvent::RoomTombstone(tomb) => { // TODO make `on_state_tombstone` method event_emitter.on_room_tombstone(room, &tomb).await } - AnyStateEventStub::Custom(custom) => { + AnySyncStateEvent::Custom(custom) => { event_emitter .on_unrecognized_event(room, &CustomOrRawEvent::State(custom)) .await @@ -1591,7 +1591,7 @@ impl BaseClient { pub(crate) async fn emit_stripped_state_event( &self, room_id: &RoomId, - event: &AnyStrippedStateEventStub, + event: &AnyStrippedStateEvent, prev_content: Option, room_state: RoomStateType, ) { @@ -1627,33 +1627,33 @@ impl BaseClient { }; match event { - AnyStrippedStateEventStub::RoomMember(member) => { + AnyStrippedStateEvent::RoomMember(member) => { event_emitter .on_stripped_state_member(room, &member, prev_content) .await } - AnyStrippedStateEventStub::RoomName(name) => { + AnyStrippedStateEvent::RoomName(name) => { event_emitter.on_stripped_state_name(room, &name).await } - AnyStrippedStateEventStub::RoomCanonicalAlias(canonical) => { + AnyStrippedStateEvent::RoomCanonicalAlias(canonical) => { event_emitter .on_stripped_state_canonical_alias(room, &canonical) .await } - AnyStrippedStateEventStub::RoomAliases(aliases) => { + AnyStrippedStateEvent::RoomAliases(aliases) => { event_emitter .on_stripped_state_aliases(room, &aliases) .await } - AnyStrippedStateEventStub::RoomAvatar(avatar) => { + AnyStrippedStateEvent::RoomAvatar(avatar) => { event_emitter.on_stripped_state_avatar(room, &avatar).await } - AnyStrippedStateEventStub::RoomPowerLevels(power) => { + AnyStrippedStateEvent::RoomPowerLevels(power) => { event_emitter .on_stripped_state_power_levels(room, &power) .await } - AnyStrippedStateEventStub::RoomJoinRules(rules) => { + AnyStrippedStateEvent::RoomJoinRules(rules) => { event_emitter .on_stripped_state_join_rules(room, &rules) .await @@ -1718,7 +1718,7 @@ impl BaseClient { pub(crate) async fn emit_ephemeral_event( &self, room_id: &RoomId, - event: &AnyEphemeralRoomEventStub, + event: &AnySyncEphemeralRoomEvent, room_state: RoomStateType, ) { let lock = self.event_emitter.read().await; @@ -1753,13 +1753,13 @@ impl BaseClient { }; match event { - AnyEphemeralRoomEventStub::FullyRead(full_read) => { + AnySyncEphemeralRoomEvent::FullyRead(full_read) => { event_emitter.on_non_room_fully_read(room, &full_read).await } - AnyEphemeralRoomEventStub::Typing(typing) => { + AnySyncEphemeralRoomEvent::Typing(typing) => { event_emitter.on_non_room_typing(room, &typing).await } - AnyEphemeralRoomEventStub::Receipt(receipt) => { + AnySyncEphemeralRoomEvent::Receipt(receipt) => { event_emitter.on_non_room_receipt(room, &receipt).await } _ => {} @@ -1838,18 +1838,20 @@ impl BaseClient { #[cfg(test)] mod test { - use crate::identifiers::{EventId, RoomId, UserId}; - use crate::{BaseClient, BaseClientConfig, Session}; - use matrix_sdk_common::events::{AnyRoomEventStub, EventJson}; + use crate::identifiers::{RoomId, UserId}; + #[cfg(feature = "messages")] + use crate::{ + events::{AnySyncRoomEvent, EventJson}, + identifiers::EventId, + BaseClientConfig, JsonStore, + }; + use crate::{BaseClient, Session}; use matrix_sdk_common_macros::async_trait; use matrix_sdk_test::{async_test, test_json, EventBuilder, EventsJson}; use serde_json::json; use std::convert::TryFrom; use tempfile::tempdir; - #[cfg(feature = "messages")] - use crate::JsonStore; - #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -2008,7 +2010,7 @@ mod test { use crate::{EventEmitter, SyncRoom}; use matrix_sdk_common::events::{ room::member::{MemberEventContent, MembershipChange}, - StateEventStub, + SyncStateEvent, }; use matrix_sdk_common::locks::RwLock; use std::sync::{ @@ -2022,7 +2024,7 @@ mod test { async fn on_room_member( &self, room: SyncRoom, - event: &StateEventStub, + event: &SyncStateEvent, ) { if let SyncRoom::Joined(_) = room { if let MembershipChange::Joined = event.membership_change() { @@ -2395,7 +2397,7 @@ mod test { "type": "m.room.redaction", "redacts": "$152037280074GZeOm:localhost" }); - let mut event: EventJson = serde_json::from_value(json).unwrap(); + let mut event: EventJson = serde_json::from_value(json).unwrap(); client .receive_joined_timeline_event(&room_id, &mut event) .await @@ -2404,8 +2406,8 @@ mod test { // check that the message has actually been redacted for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::models::FullOrRedactedEvent::Redacted( - crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + if let crate::events::AnyPossiblyRedactedSyncMessageEvent::Redacted( + crate::events::AnyRedactedSyncMessageEvent::RoomMessage(event), ) = &queue.msgs[0].deref() { // this is the id from the message event in the sync response @@ -2432,8 +2434,8 @@ mod test { // properly for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::models::FullOrRedactedEvent::Redacted( - crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + if let crate::events::AnyPossiblyRedactedSyncMessageEvent::Redacted( + crate::events::AnyRedactedSyncMessageEvent::RoomMessage(event), ) = &queue.msgs[0].deref() { // this is the id from the message event in the sync response diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index bb7c6420..610790d0 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -33,11 +33,11 @@ use crate::events::{ message::{feedback::FeedbackEventContent, MessageEventContent as MsgEventContent}, name::NameEventContent, power_levels::PowerLevelsEventContent, - redaction::RedactionEventStub, + redaction::SyncRedactionEvent, tombstone::TombstoneEventContent, }, typing::TypingEventContent, - BasicEvent, EphemeralRoomEvent, MessageEventStub, StateEventStub, StrippedStateEventStub, + BasicEvent, EphemeralRoomEvent, StrippedStateEvent, SyncMessageEvent, SyncStateEvent, }; use crate::{Room, RoomState}; use matrix_sdk_common_macros::async_trait; @@ -55,11 +55,11 @@ pub enum CustomOrRawEvent<'c> { /// A custom basic event. EphemeralRoom(&'c EphemeralRoomEvent), /// A custom room event. - Message(&'c MessageEventStub), + Message(&'c SyncMessageEvent), /// A custom state event. - State(&'c StateEventStub), + State(&'c SyncStateEvent), /// A custom stripped state event. - StrippedState(&'c StrippedStateEventStub), + StrippedState(&'c StrippedStateEvent), } /// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event. @@ -74,7 +74,7 @@ pub enum CustomOrRawEvent<'c> { /// # self, /// # events::{ /// # room::message::{MessageEventContent, TextMessageEventContent}, -/// # MessageEventStub +/// # SyncMessageEvent /// # }, /// # EventEmitter, SyncRoom /// # }; @@ -85,9 +85,9 @@ pub enum CustomOrRawEvent<'c> { /// /// #[async_trait] /// impl EventEmitter for EventCallback { -/// async fn on_room_message(&self, room: SyncRoom, event: &MessageEventStub) { +/// async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent) { /// if let SyncRoom::Joined(room) = room { -/// if let MessageEventStub { +/// if let SyncMessageEvent { /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// sender, /// .. @@ -112,114 +112,109 @@ pub enum CustomOrRawEvent<'c> { pub trait EventEmitter: Send + Sync { // ROOM EVENTS from `IncomingTimeline` /// Fires when `Client` receives a `RoomEvent::RoomMember` event. - async fn on_room_member(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_member(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomName` event. - async fn on_room_name(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_name(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomCanonicalAlias` event. async fn on_room_canonical_alias( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { } /// Fires when `Client` receives a `RoomEvent::RoomAliases` event. - async fn on_room_aliases(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_aliases(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomAvatar` event. - async fn on_room_avatar(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_avatar(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomMessage` event. - async fn on_room_message(&self, _: SyncRoom, _: &MessageEventStub) {} + async fn on_room_message(&self, _: SyncRoom, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomMessageFeedback` event. async fn on_room_message_feedback( &self, _: SyncRoom, - _: &MessageEventStub, + _: &SyncMessageEvent, ) { } /// Fires when `Client` receives a `RoomEvent::RoomRedaction` event. - async fn on_room_redaction(&self, _: SyncRoom, _: &RedactionEventStub) {} + async fn on_room_redaction(&self, _: SyncRoom, _: &SyncRedactionEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event. - async fn on_room_power_levels(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_power_levels(&self, _: SyncRoom, _: &SyncStateEvent) { } /// Fires when `Client` receives a `RoomEvent::Tombstone` event. - async fn on_room_join_rules(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_join_rules(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `RoomEvent::Tombstone` event. - async fn on_room_tombstone(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_room_tombstone(&self, _: SyncRoom, _: &SyncStateEvent) {} // `RoomEvent`s from `IncomingState` /// Fires when `Client` receives a `StateEvent::RoomMember` event. - async fn on_state_member(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_state_member(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomName` event. - async fn on_state_name(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_state_name(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomCanonicalAlias` event. async fn on_state_canonical_alias( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { } /// Fires when `Client` receives a `StateEvent::RoomAliases` event. - async fn on_state_aliases(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_state_aliases(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomAvatar` event. - async fn on_state_avatar(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_state_avatar(&self, _: SyncRoom, _: &SyncStateEvent) {} /// Fires when `Client` receives a `StateEvent::RoomPowerLevels` event. async fn on_state_power_levels( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { } /// Fires when `Client` receives a `StateEvent::RoomJoinRules` event. - async fn on_state_join_rules(&self, _: SyncRoom, _: &StateEventStub) {} + async fn on_state_join_rules(&self, _: SyncRoom, _: &SyncStateEvent) {} // `AnyStrippedStateEvent`s /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomMember` event. async fn on_stripped_state_member( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, _: Option, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomName` event. - async fn on_stripped_state_name( - &self, - _: SyncRoom, - _: &StrippedStateEventStub, - ) { - } + async fn on_stripped_state_name(&self, _: SyncRoom, _: &StrippedStateEvent) {} /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomCanonicalAlias` event. async fn on_stripped_state_canonical_alias( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomAliases` event. async fn on_stripped_state_aliases( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomAvatar` event. async fn on_stripped_state_avatar( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomPowerLevels` event. async fn on_stripped_state_power_levels( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { } /// Fires when `Client` receives a `AnyStrippedStateEvent::StrippedRoomJoinRules` event. async fn on_stripped_state_join_rules( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { } @@ -276,79 +271,79 @@ mod test { #[async_trait] impl EventEmitter for EvEmitterTest { - async fn on_room_member(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_member(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("member".to_string()) } - async fn on_room_name(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_name(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("name".to_string()) } async fn on_room_canonical_alias( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { self.0.lock().await.push("canonical".to_string()) } - async fn on_room_aliases(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_aliases(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("aliases".to_string()) } - async fn on_room_avatar(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_avatar(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("avatar".to_string()) } - async fn on_room_message(&self, _: SyncRoom, _: &MessageEventStub) { + async fn on_room_message(&self, _: SyncRoom, _: &SyncMessageEvent) { self.0.lock().await.push("message".to_string()) } async fn on_room_message_feedback( &self, _: SyncRoom, - _: &MessageEventStub, + _: &SyncMessageEvent, ) { self.0.lock().await.push("feedback".to_string()) } - async fn on_room_redaction(&self, _: SyncRoom, _: &RedactionEventStub) { + async fn on_room_redaction(&self, _: SyncRoom, _: &SyncRedactionEvent) { self.0.lock().await.push("redaction".to_string()) } async fn on_room_power_levels( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { self.0.lock().await.push("power".to_string()) } - async fn on_room_tombstone(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_room_tombstone(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("tombstone".to_string()) } - async fn on_state_member(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_state_member(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("state member".to_string()) } - async fn on_state_name(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_state_name(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("state name".to_string()) } async fn on_state_canonical_alias( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { self.0.lock().await.push("state canonical".to_string()) } - async fn on_state_aliases(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_state_aliases(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("state aliases".to_string()) } - async fn on_state_avatar(&self, _: SyncRoom, _: &StateEventStub) { + async fn on_state_avatar(&self, _: SyncRoom, _: &SyncStateEvent) { self.0.lock().await.push("state avatar".to_string()) } async fn on_state_power_levels( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { self.0.lock().await.push("state power".to_string()) } async fn on_state_join_rules( &self, _: SyncRoom, - _: &StateEventStub, + _: &SyncStateEvent, ) { self.0.lock().await.push("state rules".to_string()) } @@ -358,7 +353,7 @@ mod test { async fn on_stripped_state_member( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, _: Option, ) { self.0 @@ -370,7 +365,7 @@ mod test { async fn on_stripped_state_name( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state name".to_string()) } @@ -378,7 +373,7 @@ mod test { async fn on_stripped_state_canonical_alias( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0 .lock() @@ -389,7 +384,7 @@ mod test { async fn on_stripped_state_aliases( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0 .lock() @@ -400,7 +395,7 @@ mod test { async fn on_stripped_state_avatar( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0 .lock() @@ -411,7 +406,7 @@ mod test { async fn on_stripped_state_power_levels( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state power".to_string()) } @@ -419,7 +414,7 @@ mod test { async fn on_stripped_state_join_rules( &self, _: SyncRoom, - _: &StrippedStateEventStub, + _: &StrippedStateEvent, ) { self.0.lock().await.push("stripped state rules".to_string()) } diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index 401b258a..5458a16c 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -13,7 +13,7 @@ use std::{ use matrix_sdk_common::identifiers::EventId; use serde::{de, ser, Deserialize, Serialize}; -use crate::events::{AnyMessageEventStub, AnyRedactedMessageEventStub}; +use crate::events::{AnyRedactedSyncMessageEvent, AnySyncMessageEvent}; /// Represents either a redacted event or a non-redacted event. /// @@ -21,9 +21,9 @@ use crate::events::{AnyMessageEventStub, AnyRedactedMessageEventStub}; #[derive(Clone, Debug, Deserialize, Serialize)] pub enum FullOrRedactedEvent { /// A non-redacted event. - Full(AnyMessageEventStub), + Full(AnySyncMessageEvent), /// An event that has been redacted. - Redacted(AnyRedactedMessageEventStub), + Redacted(AnyRedactedSyncMessageEvent), } impl FullOrRedactedEvent { @@ -228,7 +228,7 @@ mod test { let json: &serde_json::Value = &test_json::MESSAGE_TEXT; let msg = FullOrRedactedEvent::Full( - serde_json::from_value::(json.clone()).unwrap(), + serde_json::from_value::(json.clone()).unwrap(), ); let mut msgs = MessageQueue::new(); @@ -275,7 +275,7 @@ mod test { let json: &serde_json::Value = &test_json::MESSAGE_TEXT; let msg = FullOrRedactedEvent::Full( - serde_json::from_value::(json.clone()).unwrap(), + serde_json::from_value::(json.clone()).unwrap(), ); let mut msgs = MessageQueue::new(); diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 3be5980d..1590042b 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -15,6 +15,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryFrom; +#[cfg(feature = "messages")] +use std::ops::DerefMut; use serde::{Deserialize, Serialize}; use tracing::{debug, error, trace}; @@ -22,27 +24,26 @@ use tracing::{debug, error, trace}; #[cfg(feature = "messages")] use super::message::{FullOrRedactedEvent, MessageQueue}; use super::RoomMember; - use crate::api::r0::sync::sync_events::{RoomSummary, UnreadNotificationsCount}; use crate::events::{ - presence::PresenceEvent, + presence::{PresenceEvent, PresenceEventContent}, room::{ aliases::AliasesEventContent, canonical_alias::CanonicalAliasEventContent, encryption::EncryptionEventContent, - member::{MemberEventContent, MembershipChange}, + member::{MemberEventContent, MembershipChange, MembershipState}, name::NameEventContent, power_levels::{NotificationPowerLevels, PowerLevelsEventContent}, tombstone::TombstoneEventContent, }, - Algorithm, AnyRoomEventStub, AnyStateEventStub, AnyStrippedStateEventStub, EventType, - StateEventStub, StrippedStateEventStub, + Algorithm, AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, + StrippedStateEvent, SyncStateEvent, }; #[cfg(feature = "messages")] use crate::events::{ - room::redaction::{RedactionEvent, RedactionEventStub}, - AnyMessageEventStub, EventJson, + room::redaction::{RedactionEvent, SyncRedactionEvent}, + AnySyncMessageEvent, }; use crate::identifiers::{RoomAliasId, RoomId, UserId}; @@ -51,7 +52,7 @@ use crate::js_int::{int, uint, Int, UInt}; #[cfg(feature = "messages")] fn redaction_event_from_redaction_stub( - event: RedactionEventStub, + event: SyncRedactionEvent, room_id: RoomId, ) -> RedactionEvent { RedactionEvent { @@ -145,8 +146,8 @@ impl EncryptionInfo { } } -impl From<&StateEventStub> for EncryptionInfo { - fn from(event: &StateEventStub) -> Self { +impl From<&SyncStateEvent> for EncryptionInfo { + fn from(event: &SyncStateEvent) -> Self { EncryptionInfo { algorithm: event.content.algorithm.clone(), rotation_period_ms: event @@ -351,7 +352,7 @@ impl Room { fn add_member( &mut self, target_member: &UserId, - event: &StateEventStub, + event: &SyncStateEvent, ) -> (bool, HashMap) { let new_member = RoomMember::new(event, &self.room_id); @@ -405,7 +406,7 @@ impl Room { fn remove_member( &mut self, target_member: &UserId, - event: &StateEventStub, + event: &SyncStateEvent, ) -> (bool, HashMap) { let leaving_member = RoomMember::new(event, &self.room_id); @@ -554,7 +555,7 @@ impl Room { true } - fn set_room_power_level(&mut self, event: &StateEventStub) -> bool { + fn set_room_power_level(&mut self, event: &SyncStateEvent) -> bool { let PowerLevelsEventContent { ban, events, @@ -608,7 +609,7 @@ impl Room { /// `disambiguation_updates`). pub fn handle_membership( &mut self, - event: &StateEventStub, + event: &SyncStateEvent, state_event: bool, ) -> (bool, HashMap) { use MembershipChange::*; @@ -672,7 +673,7 @@ impl Room { /// Returns true if `MessageQueue` was added to. #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] - pub fn handle_message(&mut self, event: &AnyMessageEventStub) -> bool { + pub fn handle_message(&mut self, event: &AnySyncMessageEvent) -> bool { self.messages.push(FullOrRedactedEvent::Full(event.clone())) } @@ -684,12 +685,10 @@ impl Room { /// field. #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] - pub fn handle_redaction(&mut self, redacted_event: &RedactionEventStub) -> bool { + pub fn handle_redaction(&mut self, redacted_event: &SyncRedactionEvent) -> bool { use matrix_sdk_common::events::{ - AnyRedactedMessageEventStub, MessageEventStub, RedactedMessageEventStub, + AnyRedactedSyncMessageEvent, EventJson, RedactedSyncMessageEvent, SyncMessageEvent, }; - use std::ops::DerefMut; - if let Some(msg) = self .messages .iter_mut() @@ -697,8 +696,8 @@ impl Room { { match msg.deref_mut() { FullOrRedactedEvent::Full(event) => match event { - AnyMessageEventStub::RoomMessage(event) => { - let MessageEventStub { + AnySyncMessageEvent::RoomMessage(event) => { + let SyncMessageEvent { content, event_id, sender, @@ -712,7 +711,7 @@ impl Room { ))); let redacted = content.redact(); msg.0 = FullOrRedactedEvent::Redacted( - AnyRedactedMessageEventStub::RoomMessage(RedactedMessageEventStub { + AnyRedactedSyncMessageEvent::RoomMessage(RedactedSyncMessageEvent { content: redacted, event_id, origin_server_ts, @@ -721,8 +720,8 @@ impl Room { }), ) } - AnyMessageEventStub::Sticker(event) => { - let MessageEventStub { + AnySyncMessageEvent::Sticker(event) => { + let SyncMessageEvent { content, event_id, sender, @@ -735,8 +734,8 @@ impl Room { self.room_id.clone(), ))); let redacted = content.redact(); - msg.0 = FullOrRedactedEvent::Redacted(AnyRedactedMessageEventStub::Sticker( - RedactedMessageEventStub { + msg.0 = FullOrRedactedEvent::Redacted(AnyRedactedSyncMessageEvent::Sticker( + RedactedSyncMessageEvent { content: redacted, event_id, origin_server_ts, @@ -759,7 +758,7 @@ impl Room { /// Handle a room.aliases event, updating the room state if necessary. /// /// Returns true if the room name changed, false otherwise. - pub fn handle_room_aliases(&mut self, event: &StateEventStub) -> bool { + pub fn handle_room_aliases(&mut self, event: &SyncStateEvent) -> bool { match event.content.aliases.as_slice() { [alias] => self.push_room_alias(alias), [alias, ..] => self.push_room_alias(alias), @@ -771,7 +770,7 @@ impl Room { /// necessary. /// /// Returns true if the room name changed, false otherwise. - pub fn handle_canonical(&mut self, event: &StateEventStub) -> bool { + pub fn handle_canonical(&mut self, event: &SyncStateEvent) -> bool { match &event.content.alias { Some(name) => self.canonical_alias(&name), _ => false, @@ -781,7 +780,7 @@ impl Room { /// Handle a room.name event, updating the room state if necessary. /// /// Returns true if the room name changed, false otherwise. - pub fn handle_room_name(&mut self, event: &StateEventStub) -> bool { + pub fn handle_room_name(&mut self, event: &SyncStateEvent) -> bool { match event.content.name() { Some(name) => self.set_room_name(name), _ => false, @@ -793,7 +792,7 @@ impl Room { /// Returns true if the room name changed, false otherwise. pub fn handle_stripped_room_name( &mut self, - event: &StrippedStateEventStub, + event: &StrippedStateEvent, ) -> bool { match event.content.name() { Some(name) => self.set_room_name(name), @@ -804,7 +803,7 @@ impl Room { /// Handle a room.power_levels event, updating the room state if necessary. /// /// Returns true if the room name changed, false otherwise. - pub fn handle_power_level(&mut self, event: &StateEventStub) -> bool { + pub fn handle_power_level(&mut self, event: &SyncStateEvent) -> bool { // NOTE: this is always true, we assume that if we get an event their is an update. let mut updated = self.set_room_power_level(event); @@ -824,7 +823,7 @@ impl Room { updated } - fn handle_tombstone(&mut self, event: &StateEventStub) -> bool { + fn handle_tombstone(&mut self, event: &SyncStateEvent) -> bool { self.tombstone = Some(Tombstone { body: event.content.body.clone(), replacement: event.content.replacement_room.clone(), @@ -832,7 +831,7 @@ impl Room { true } - fn handle_encryption_event(&mut self, event: &StateEventStub) -> bool { + fn handle_encryption_event(&mut self, event: &SyncStateEvent) -> bool { self.encrypted = Some(event.into()); true } @@ -844,31 +843,31 @@ impl Room { /// # Arguments /// /// * `event` - The event of the room. - pub fn receive_timeline_event(&mut self, event: &AnyRoomEventStub) -> bool { + pub fn receive_timeline_event(&mut self, event: &AnySyncRoomEvent) -> bool { match event { - AnyRoomEventStub::State(event) => match event { + AnySyncRoomEvent::State(event) => match event { // update to the current members of the room - AnyStateEventStub::RoomMember(event) => self.handle_membership(event, false).0, + AnySyncStateEvent::RoomMember(event) => self.handle_membership(event, false).0, // finds all events related to the name of the room for later use - AnyStateEventStub::RoomName(event) => self.handle_room_name(event), - AnyStateEventStub::RoomCanonicalAlias(event) => self.handle_canonical(event), - AnyStateEventStub::RoomAliases(event) => self.handle_room_aliases(event), + AnySyncStateEvent::RoomName(event) => self.handle_room_name(event), + AnySyncStateEvent::RoomCanonicalAlias(event) => self.handle_canonical(event), + AnySyncStateEvent::RoomAliases(event) => self.handle_room_aliases(event), // power levels of the room members - AnyStateEventStub::RoomPowerLevels(event) => self.handle_power_level(event), - AnyStateEventStub::RoomTombstone(event) => self.handle_tombstone(event), - AnyStateEventStub::RoomEncryption(event) => self.handle_encryption_event(event), + AnySyncStateEvent::RoomPowerLevels(event) => self.handle_power_level(event), + AnySyncStateEvent::RoomTombstone(event) => self.handle_tombstone(event), + AnySyncStateEvent::RoomEncryption(event) => self.handle_encryption_event(event), _ => false, }, - AnyRoomEventStub::Message(event) => match event { + AnySyncRoomEvent::Message(event) => match event { #[cfg(feature = "messages")] // We ignore this variants event because `handle_message` takes the enum - // to store AnyMessageEventStub events in the `MessageQueue`. - AnyMessageEventStub::RoomMessage(_) => self.handle_message(event), + // to store AnySyncMessageEvent events in the `MessageQueue`. + AnySyncMessageEvent::RoomMessage(_) => self.handle_message(event), #[cfg(feature = "messages")] - AnyMessageEventStub::RoomRedaction(event) => self.handle_redaction(event), + AnySyncMessageEvent::RoomRedaction(event) => self.handle_redaction(event), _ => false, }, - AnyRoomEventStub::RedactedMessage(_) | AnyRoomEventStub::RedactedState(_) => false, + AnySyncRoomEvent::RedactedMessage(_) | AnySyncRoomEvent::RedactedState(_) => false, } } @@ -879,18 +878,18 @@ impl Room { /// # Arguments /// /// * `event` - The event of the room. - pub fn receive_state_event(&mut self, event: &AnyStateEventStub) -> bool { + pub fn receive_state_event(&mut self, event: &AnySyncStateEvent) -> bool { match event { // update to the current members of the room - AnyStateEventStub::RoomMember(member) => self.handle_membership(member, true).0, + AnySyncStateEvent::RoomMember(member) => self.handle_membership(member, true).0, // finds all events related to the name of the room for later use - AnyStateEventStub::RoomName(name) => self.handle_room_name(name), - AnyStateEventStub::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), - AnyStateEventStub::RoomAliases(alias) => self.handle_room_aliases(alias), + AnySyncStateEvent::RoomName(name) => self.handle_room_name(name), + AnySyncStateEvent::RoomCanonicalAlias(c_alias) => self.handle_canonical(c_alias), + AnySyncStateEvent::RoomAliases(alias) => self.handle_room_aliases(alias), // power levels of the room members - AnyStateEventStub::RoomPowerLevels(power) => self.handle_power_level(power), - AnyStateEventStub::RoomTombstone(tomb) => self.handle_tombstone(tomb), - AnyStateEventStub::RoomEncryption(encrypt) => self.handle_encryption_event(encrypt), + AnySyncStateEvent::RoomPowerLevels(power) => self.handle_power_level(power), + AnySyncStateEvent::RoomTombstone(tomb) => self.handle_tombstone(tomb), + AnySyncStateEvent::RoomEncryption(encrypt) => self.handle_encryption_event(encrypt), _ => false, } } @@ -903,9 +902,9 @@ impl Room { /// /// * `event` - The `AnyStrippedStateEvent` sent by the server for invited /// but not joined rooms. - pub fn receive_stripped_state_event(&mut self, event: &AnyStrippedStateEventStub) -> bool { + pub fn receive_stripped_state_event(&mut self, event: &AnyStrippedStateEvent) -> bool { match event { - AnyStrippedStateEventStub::RoomName(event) => self.handle_stripped_room_name(event), + AnyStrippedStateEvent::RoomName(event) => self.handle_stripped_room_name(event), _ => false, } } @@ -977,7 +976,7 @@ impl Room { pub fn update_member_profile( &mut self, target_member: &UserId, - event: &StateEventStub, + event: &SyncStateEvent, change: MembershipChange, ) -> (bool, HashMap) { let member = self.get_member(target_member); @@ -1067,7 +1066,7 @@ impl Room { /// * `max_power` - Maximum power level allowed. pub fn update_member_power( member: &mut RoomMember, - event: &StateEventStub, + event: &SyncStateEvent, max_power: Int, ) -> bool { let changed; @@ -1142,7 +1141,7 @@ impl Describe for MembershipChange { #[cfg(test)] mod test { use super::*; - use crate::events::{room::encryption::EncryptionEventContent, UnsignedData}; + use crate::events::{room::encryption::EncryptionEventContent, EventJson, UnsignedData}; use crate::identifiers::{EventId, UserId}; use crate::{BaseClient, Session}; use matrix_sdk_test::{async_test, sync_response, EventBuilder, EventsJson, SyncResponseFile}; @@ -1707,7 +1706,7 @@ mod test { "type": "m.room.redaction", "redacts": "$152037280074GZeOm:localhost" }); - let mut event: EventJson = serde_json::from_value(json).unwrap(); + let mut event: EventJson = serde_json::from_value(json).unwrap(); client .receive_joined_timeline_event(&room_id, &mut event) .await @@ -1716,7 +1715,7 @@ mod test { for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; if let crate::models::message::FullOrRedactedEvent::Redacted( - crate::events::AnyRedactedMessageEventStub::RoomMessage(event), + crate::events::AnyRedactedSyncMessageEvent::RoomMessage(event), ) = &queue.msgs[0].deref() { // this is the id from the message event in the sync response @@ -1747,7 +1746,7 @@ mod test { client.restore_login(session).await.unwrap(); client.receive_sync_response(&mut response).await.unwrap(); - let event = StateEventStub { + let event = SyncStateEvent { event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), origin_server_ts: SystemTime::now(), sender: user_id, diff --git a/matrix_sdk_base/src/models/room_member.rs b/matrix_sdk_base/src/models/room_member.rs index 999ac68c..cd45f866 100644 --- a/matrix_sdk_base/src/models/room_member.rs +++ b/matrix_sdk_base/src/models/room_member.rs @@ -15,12 +15,15 @@ use std::convert::TryFrom; -use crate::events::presence::{PresenceEvent, PresenceState}; -use crate::events::room::member::MemberEventContent; -use crate::events::StateEventStub; -use crate::identifiers::{RoomId, UserId}; - -use crate::js_int::{Int, UInt}; +use matrix_sdk_common::{ + events::{ + presence::{PresenceEvent, PresenceState}, + room::member::MemberEventContent, + SyncStateEvent, + }, + identifiers::{RoomId, UserId}, + js_int::{Int, UInt}, +}; use serde::{Deserialize, Serialize}; // Notes: if Alice invites Bob into a room we will get an event with the sender as Alice and the state key as Bob. @@ -64,7 +67,7 @@ pub struct RoomMember { // // Needs design. /// The events that created the state of this room member. - pub events: Vec>, + pub events: Vec>, /// The `PresenceEvent`s connected to this user. pub presence_events: Vec, } @@ -83,7 +86,7 @@ impl PartialEq for RoomMember { } impl RoomMember { - pub fn new(event: &StateEventStub, room_id: &RoomId) -> Self { + pub fn new(event: &SyncStateEvent, room_id: &RoomId) -> Self { Self { name: event.state_key.clone(), room_id: room_id.clone(), diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index b1ae4696..05b0a9d1 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -17,7 +17,7 @@ js_int = "0.1.8" [dependencies.ruma] git = "https://github.com/ruma/ruma" features = ["client-api"] -rev = "5e428ac" +rev = "848b225" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] uuid = { version = "0.8.1", features = ["v4"] } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 556a599b..902cdb39 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -37,7 +37,7 @@ use matrix_sdk_common::events::{ room::message::MessageEventContent, room_key::RoomKeyEventContent, room_key_request::RoomKeyRequestEventContent, - Algorithm, AnyRoomEventStub, AnyToDeviceEvent, EventJson, EventType, MessageEventStub, + Algorithm, AnySyncRoomEvent, AnyToDeviceEvent, EventJson, EventType, SyncMessageEvent, ToDeviceEvent, }; use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; @@ -1135,9 +1135,9 @@ impl OlmMachine { /// * `room_id` - The ID of the room where the event was sent to. pub async fn decrypt_room_event( &mut self, - event: &MessageEventStub, + event: &SyncMessageEvent, room_id: &RoomId, - ) -> MegolmResult> { + ) -> MegolmResult> { let content = match &event.content { EncryptedEventContent::MegolmV1AesSha2(c) => c, _ => return Err(EventError::UnsupportedAlgorithm.into()), @@ -1244,8 +1244,8 @@ mod test { encrypted::EncryptedEventContent, message::{MessageEventContent, TextMessageEventContent}, }, - AnyMessageEventStub, AnyRoomEventStub, AnyToDeviceEvent, EventJson, EventType, - MessageEventStub, ToDeviceEvent, UnsignedData, + AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent, EventJson, EventType, + SyncMessageEvent, ToDeviceEvent, Unsigned, }; use matrix_sdk_common::identifiers::{DeviceId, EventId, RoomId, UserId}; use matrix_sdk_test::test_json; @@ -1762,12 +1762,12 @@ mod test { let encrypted_content = alice.encrypt(&room_id, content.clone()).await.unwrap(); - let event = MessageEventStub { + let event = SyncMessageEvent { event_id: EventId::try_from("$xxxxx:example.org").unwrap(), origin_server_ts: SystemTime::now(), sender: alice.user_id().clone(), content: encrypted_content, - unsigned: UnsignedData::default(), + unsigned: Unsigned::default(), }; let decrypted_event = bob @@ -1778,7 +1778,7 @@ mod test { .unwrap(); match decrypted_event { - AnyRoomEventStub::Message(AnyMessageEventStub::RoomMessage(MessageEventStub { + AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(SyncMessageEvent { sender, content, .. diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index 3755e5d7..2a33ce3a 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -6,8 +6,8 @@ use http::Response; use matrix_sdk_common::api::r0::sync::sync_events::Response as SyncResponse; use matrix_sdk_common::events::{ - presence::PresenceEvent, AnyBasicEvent, AnyEphemeralRoomEventStub, AnyRoomEventStub, - AnyStateEventStub, + presence::PresenceEvent, AnyBasicEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, + AnySyncStateEvent, }; use matrix_sdk_common::identifiers::RoomId; use serde_json::Value as JsonValue; @@ -80,17 +80,17 @@ pub enum EventsJson { #[derive(Default)] pub struct EventBuilder { /// The events that determine the state of a `Room`. - joined_room_events: HashMap>, + joined_room_events: HashMap>, /// The events that determine the state of a `Room`. - invited_room_events: HashMap>, + invited_room_events: HashMap>, /// The events that determine the state of a `Room`. - left_room_events: HashMap>, + left_room_events: HashMap>, /// The presence events that determine the presence state of a `RoomMember`. presence_events: Vec, /// The state events that determine the state of a `Room`. - state_events: Vec, + state_events: Vec, /// The ephemeral room events that determine the state of a `Room`. - ephemeral: Vec, + ephemeral: Vec, /// The account data events that determine the state of a `Room`. account_data: Vec, /// Internal counter to enable the `prev_batch` and `next_batch` of each sync response to vary. @@ -110,7 +110,7 @@ impl EventBuilder { _ => panic!("unknown ephemeral event {:?}", json), }; - let event = serde_json::from_value::(val.clone()).unwrap(); + let event = serde_json::from_value::(val.clone()).unwrap(); self.ephemeral.push(event); self } @@ -136,7 +136,7 @@ impl EventBuilder { _ => panic!("unknown room event json {:?}", json), }; - let event = serde_json::from_value::(val.clone()).unwrap(); + let event = serde_json::from_value::(val.clone()).unwrap(); self.add_joined_event( &RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap(), @@ -150,12 +150,12 @@ impl EventBuilder { room_id: &RoomId, event: serde_json::Value, ) -> &mut Self { - let event = serde_json::from_value::(event).unwrap(); + let event = serde_json::from_value::(event).unwrap(); self.add_joined_event(room_id, event); self } - fn add_joined_event(&mut self, room_id: &RoomId, event: AnyRoomEventStub) { + fn add_joined_event(&mut self, room_id: &RoomId, event: AnySyncRoomEvent) { self.joined_room_events .entry(room_id.clone()) .or_insert_with(Vec::new) @@ -167,7 +167,7 @@ impl EventBuilder { room_id: &RoomId, event: serde_json::Value, ) -> &mut Self { - let event = serde_json::from_value::(event).unwrap(); + let event = serde_json::from_value::(event).unwrap(); self.invited_room_events .entry(room_id.clone()) .or_insert_with(Vec::new) @@ -180,7 +180,7 @@ impl EventBuilder { room_id: &RoomId, event: serde_json::Value, ) -> &mut Self { - let event = serde_json::from_value::(event).unwrap(); + let event = serde_json::from_value::(event).unwrap(); self.left_room_events .entry(room_id.clone()) .or_insert_with(Vec::new) @@ -199,7 +199,7 @@ impl EventBuilder { _ => panic!("unknown state event {:?}", json), }; - let event = serde_json::from_value::(val.clone()).unwrap(); + let event = serde_json::from_value::(val.clone()).unwrap(); self.state_events.push(event); self } From 807435c043be6e41f56ca99cf6d76463bd7cb11e Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 18 Jul 2020 08:51:19 -0400 Subject: [PATCH 66/83] Updates DeviceId to be Box --- matrix_sdk/src/client.rs | 6 +-- matrix_sdk/src/request_builder.rs | 4 +- matrix_sdk_common/Cargo.toml | 2 +- matrix_sdk_crypto/src/device.rs | 14 ++++--- matrix_sdk_crypto/src/error.rs | 10 ++--- matrix_sdk_crypto/src/lib.rs | 2 +- matrix_sdk_crypto/src/machine.rs | 37 +++++++--------- matrix_sdk_crypto/src/memory_stores.rs | 17 +++++--- matrix_sdk_crypto/src/olm.rs | 58 ++++++++++++++------------ matrix_sdk_crypto/src/store/sqlite.rs | 12 +++--- 10 files changed, 85 insertions(+), 77 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 9b2f213e..8ee4c134 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -472,7 +472,7 @@ impl Client { login_info: login::LoginInfo::Password { password: password.into(), }, - device_id: device_id.map(|d| d.into()), + device_id: device_id.map(|d| d.into().into_boxed_str()), initial_device_display_name: initial_device_display_name.map(|d| d.into()), }; @@ -1407,7 +1407,7 @@ impl Client { #[instrument] async fn claim_one_time_keys( &self, - one_time_keys: BTreeMap>, + one_time_keys: BTreeMap, KeyAlgorithm>>, ) -> Result { let request = claim_keys::Request { timeout: None, @@ -1511,7 +1511,7 @@ impl Client { users_for_query ); - let mut device_keys: BTreeMap> = BTreeMap::new(); + let mut device_keys: BTreeMap>> = BTreeMap::new(); for user in users_for_query.drain() { device_keys.insert(user, Vec::new()); diff --git a/matrix_sdk/src/request_builder.rs b/matrix_sdk/src/request_builder.rs index d397d78f..9d0cf748 100644 --- a/matrix_sdk/src/request_builder.rs +++ b/matrix_sdk/src/request_builder.rs @@ -275,7 +275,7 @@ impl Into for MessagesRequestBuilder { pub struct RegistrationBuilder { password: Option, username: Option, - device_id: Option, + device_id: Option>, initial_device_display_name: Option, auth: Option, kind: Option, @@ -309,7 +309,7 @@ impl RegistrationBuilder { /// /// If this does not correspond to a known client device, a new device will be created. /// The server will auto-generate a device_id if this is not specified. - pub fn device_id>(&mut self, device_id: S) -> &mut Self { + pub fn device_id>>(&mut self, device_id: S) -> &mut Self { self.device_id = Some(device_id.into()); self } diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 05b0a9d1..2dd86448 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -17,7 +17,7 @@ js_int = "0.1.8" [dependencies.ruma] git = "https://github.com/ruma/ruma" features = ["client-api"] -rev = "848b225" +rev = "848b22568106d05c5444f3fe46070d5aa16e422b" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] uuid = { version = "0.8.1", features = ["v4"] } diff --git a/matrix_sdk_crypto/src/device.rs b/matrix_sdk_crypto/src/device.rs index e7a5ec14..17804bca 100644 --- a/matrix_sdk_crypto/src/device.rs +++ b/matrix_sdk_crypto/src/device.rs @@ -33,7 +33,7 @@ use crate::verify_json; #[derive(Debug, Clone)] pub struct Device { user_id: Arc, - device_id: Arc, + device_id: Arc>, algorithms: Arc>, keys: Arc>, display_name: Arc>, @@ -70,7 +70,7 @@ impl Device { /// Create a new Device. pub fn new( user_id: UserId, - device_id: DeviceId, + device_id: Box, display_name: Option, trust_state: TrustState, algorithms: Vec, @@ -104,8 +104,10 @@ impl Device { /// Get the key of the given key algorithm belonging to this device. pub fn get_key(&self, algorithm: KeyAlgorithm) -> Option<&String> { - self.keys - .get(&AlgorithmAndDeviceId(algorithm, self.device_id.to_string())) + self.keys.get(&AlgorithmAndDeviceId( + algorithm, + self.device_id.as_ref().clone(), + )) } /// Get a map containing all the device keys. @@ -180,7 +182,7 @@ impl From<&OlmMachine> for Device { fn from(machine: &OlmMachine) -> Self { Device { user_id: Arc::new(machine.user_id().clone()), - device_id: Arc::new(machine.device_id().clone()), + device_id: Arc::new(machine.device_id().into()), algorithms: Arc::new(vec![ Algorithm::MegolmV1AesSha2, Algorithm::OlmV1Curve25519AesSha2, @@ -193,7 +195,7 @@ impl From<&OlmMachine> for Device { ( AlgorithmAndDeviceId( KeyAlgorithm::try_from(key.as_ref()).unwrap(), - machine.device_id().clone(), + machine.device_id().into(), ), value.to_owned(), ) diff --git a/matrix_sdk_crypto/src/error.rs b/matrix_sdk_crypto/src/error.rs index 4dcc207b..5ec901c3 100644 --- a/matrix_sdk_crypto/src/error.rs +++ b/matrix_sdk_crypto/src/error.rs @@ -128,21 +128,21 @@ pub(crate) enum SessionCreationError { "Failed to create a new Olm session for {0} {1}, the requested \ one-time key isn't a signed curve key" )] - OneTimeKeyNotSigned(UserId, DeviceId), + OneTimeKeyNotSigned(UserId, Box), #[error( "Tried to create a new Olm session for {0} {1}, but the signed \ one-time key is missing" )] - OneTimeKeyMissing(UserId, DeviceId), + OneTimeKeyMissing(UserId, Box), #[error("Failed to verify the one-time key signatures for {0} {1}: {2:?}")] - InvalidSignature(UserId, DeviceId, SignatureError), + InvalidSignature(UserId, Box, SignatureError), #[error( "Tried to create an Olm session for {0} {1}, but the device is missing \ a curve25519 key" )] - DeviceMissingCurveKey(UserId, DeviceId), + DeviceMissingCurveKey(UserId, Box), #[error("Error creating new Olm session for {0} {1}: {2:?}")] - OlmError(UserId, DeviceId, OlmSessionError), + OlmError(UserId, Box, OlmSessionError), } impl From for SignatureError { diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index cd5556bc..148480ca 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -82,7 +82,7 @@ pub(crate) fn verify_json( json_object.insert("unsigned".to_string(), u); } - let key_id = AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, key_id.to_string()); + let key_id = AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, key_id.into()); let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?; let signature_object = signatures diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 902cdb39..6bbbce33 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -64,7 +64,7 @@ pub struct OlmMachine { /// The unique user id that owns this account. user_id: UserId, /// The unique device id of the device that holds this account. - device_id: DeviceId, + device_id: Box, /// Our underlying Olm Account holding our identity keys. account: Account, /// Store for the encryption keys. @@ -102,7 +102,7 @@ impl OlmMachine { pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self { OlmMachine { user_id: user_id.clone(), - device_id: device_id.to_owned(), + device_id: device_id.into(), account: Account::new(user_id, &device_id), store: Box::new(MemoryStore::new()), outbound_group_sessions: HashMap::new(), @@ -128,7 +128,7 @@ impl OlmMachine { /// the encryption keys. pub async fn new_with_store( user_id: UserId, - device_id: String, + device_id: Box, mut store: Box, ) -> StoreResult { let account = match store.load_account().await? { @@ -171,7 +171,7 @@ impl OlmMachine { let store = SqliteStore::open_with_passphrase(&user_id, device_id, path, passphrase).await?; - OlmMachine::new_with_store(user_id.to_owned(), device_id.to_owned(), Box::new(store)).await + OlmMachine::new_with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await } /// The unique user id that owns this identity. @@ -255,7 +255,7 @@ impl OlmMachine { pub async fn get_missing_sessions( &mut self, users: impl Iterator, - ) -> OlmResult>> { + ) -> OlmResult, KeyAlgorithm>>> { let mut missing = BTreeMap::new(); for user_id in users { @@ -282,10 +282,8 @@ impl OlmMachine { } let user_map = missing.get_mut(user_id).unwrap(); - let _ = user_map.insert( - device.device_id().to_owned(), - KeyAlgorithm::SignedCurve25519, - ); + let _ = + user_map.insert(device.device_id().into(), KeyAlgorithm::SignedCurve25519); } } } @@ -356,7 +354,7 @@ impl OlmMachine { async fn handle_devices_from_key_query( &mut self, - device_keys_map: &BTreeMap>, + device_keys_map: &BTreeMap, DeviceKeys>>, ) -> StoreResult> { let mut changed_devices = Vec::new(); @@ -406,7 +404,8 @@ impl OlmMachine { changed_devices.push(device); } - let current_devices: HashSet<&DeviceId> = device_map.keys().collect(); + let current_devices: HashSet<&DeviceId> = + device_map.keys().map(|id| id.as_ref()).collect(); let stored_devices = self.store.get_user_devices(&user_id).await.unwrap(); let stored_devices_set: HashSet<&DeviceId> = stored_devices.keys().collect(); @@ -843,10 +842,7 @@ impl OlmMachine { let message_type: usize = ciphertext.0.into(); - let ciphertext = CiphertextInfo { - body: ciphertext.1, - message_type: (message_type as u32).into(), - }; + let ciphertext = CiphertextInfo::new(ciphertext.1, (message_type as u32).into()); let mut content = BTreeMap::new(); @@ -855,10 +851,7 @@ impl OlmMachine { self.store.save_sessions(&[session]).await?; Ok(EncryptedEventContent::OlmV1Curve25519AesSha2( - OlmV1Curve25519AesSha2Content { - sender_key: identity_keys.curve25519().to_owned(), - ciphertext: content, - }, + OlmV1Curve25519AesSha2Content::new(content, identity_keys.curve25519().to_owned()), )) } @@ -989,7 +982,7 @@ impl OlmMachine { .await?; user_messages.insert( - DeviceIdOrAllDevices::DeviceId(device.device_id().clone()), + DeviceIdOrAllDevices::DeviceId(device.device_id().into()), serde_json::value::to_raw_value(&encrypted_content)?, ); } @@ -1254,8 +1247,8 @@ mod test { UserId::try_from("@alice:example.org").unwrap() } - fn alice_device_id() -> DeviceId { - "JLAFKJWSCS".to_string() + fn alice_device_id() -> Box { + "JLAFKJWSCS".into() } fn user_id() -> UserId { diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index f38f476e..51303864 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -129,13 +129,13 @@ impl GroupSessionStore { /// In-memory store holding the devices of users. #[derive(Clone, Debug, Default)] pub struct DeviceStore { - entries: Arc>>, + entries: Arc, Device>>>, } /// A read only view over all devices belonging to a user. #[derive(Debug)] pub struct UserDevices { - entries: ReadOnlyView, + entries: ReadOnlyView, Device>, } impl UserDevices { @@ -146,7 +146,7 @@ impl UserDevices { /// Iterator over all the device ids of the user devices. pub fn keys(&self) -> impl Iterator { - self.entries.keys() + self.entries.keys().map(|id| id.as_ref()) } /// Iterator over all the devices of the user devices. @@ -175,7 +175,9 @@ impl DeviceStore { let device_map = self.entries.get_mut(&user_id).unwrap(); device_map - .insert(device.device_id().to_owned(), device) + // TODO this is ok if this is for sure a valid device_id otherwise + // Box::::try_from(&str) is the validated version + .insert(device.device_id().into(), device) .is_none() } @@ -202,7 +204,12 @@ impl DeviceStore { self.entries.insert(user_id.clone(), DashMap::new()); } UserDevices { - entries: self.entries.get(user_id).unwrap().clone().into_read_only(), + entries: self + .entries + .get(user_id) + .map(|d| d.clone()) // TODO I'm sure this is not ok but I'm not sure what to do?? + .unwrap() + .into_read_only(), } } } diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs index 5c4f6f0d..106445c6 100644 --- a/matrix_sdk_crypto/src/olm.rs +++ b/matrix_sdk_crypto/src/olm.rs @@ -40,7 +40,6 @@ pub use olm_rs::{ utility::OlmUtility, }; -use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; use matrix_sdk_common::{ api::r0::keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey}, events::{ @@ -48,8 +47,9 @@ use matrix_sdk_common::{ encrypted::{EncryptedEventContent, MegolmV1AesSha2Content}, message::MessageEventContent, }, - Algorithm, AnyRoomEventStub, EventJson, EventType, MessageEventStub, + Algorithm, AnySyncRoomEvent, EventJson, EventType, SyncMessageEvent, }, + identifiers::{DeviceId, RoomId, UserId}, }; /// Account holding identity keys for which sessions can be created. @@ -59,7 +59,7 @@ use matrix_sdk_common::{ #[derive(Clone)] pub struct Account { user_id: Arc, - device_id: Arc, + device_id: Arc>, inner: Arc>, identity_keys: Arc, shared: Arc, @@ -94,7 +94,7 @@ impl Account { Account { user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.to_owned()), + device_id: Arc::new(device_id.into()), inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), shared: Arc::new(AtomicBool::new(false)), @@ -267,7 +267,7 @@ impl Account { Ok(Account { user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.to_owned()), + device_id: Arc::new(device_id.into()), inner: Arc::new(Mutex::new(account)), identity_keys: Arc::new(identity_keys), shared: Arc::new(AtomicBool::from(shared)), @@ -371,7 +371,7 @@ impl Account { }; one_time_key_map.insert( - AlgorithmAndDeviceId(KeyAlgorithm::SignedCurve25519, key_id.to_owned()), + AlgorithmAndDeviceId(KeyAlgorithm::SignedCurve25519, key_id.as_str().into()), OneTimeKey::SignedKey(signed_key), ); } @@ -431,7 +431,7 @@ impl Account { let one_time_key = key_map.values().next().ok_or_else(|| { SessionCreationError::OneTimeKeyMissing( device.user_id().to_owned(), - device.device_id().to_owned(), + device.device_id().into(), ) })?; @@ -440,7 +440,7 @@ impl Account { OneTimeKey::Key(_) => { return Err(SessionCreationError::OneTimeKeyNotSigned( device.user_id().to_owned(), - device.device_id().to_owned(), + device.device_id().into(), )); } }; @@ -448,7 +448,7 @@ impl Account { device.verify_one_time_key(&one_time_key).map_err(|e| { SessionCreationError::InvalidSignature( device.user_id().to_owned(), - device.device_id().to_owned(), + device.device_id().into(), e, ) })?; @@ -456,7 +456,7 @@ impl Account { let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or_else(|| { SessionCreationError::DeviceMissingCurveKey( device.user_id().to_owned(), - device.device_id().to_owned(), + device.device_id().into(), ) })?; @@ -465,7 +465,7 @@ impl Account { .map_err(|e| { SessionCreationError::OlmError( device.user_id().to_owned(), - device.device_id().to_owned(), + device.device_id().into(), e, ) }) @@ -821,8 +821,8 @@ impl InboundGroupSession { /// * `event` - The event that should be decrypted. pub async fn decrypt( &self, - event: &MessageEventStub, - ) -> MegolmResult<(EventJson, u32)> { + event: &SyncMessageEvent, + ) -> MegolmResult<(EventJson, u32)> { let content = match &event.content { EncryptedEventContent::MegolmV1AesSha2(c) => c, _ => return Err(EventError::UnsupportedAlgorithm.into()), @@ -853,7 +853,7 @@ impl InboundGroupSession { ); Ok(( - serde_json::from_value::>(decrypted_value)?, + serde_json::from_value::>(decrypted_value)?, message_index, )) } @@ -882,7 +882,7 @@ impl PartialEq for InboundGroupSession { #[derive(Clone)] pub struct OutboundGroupSession { inner: Arc>, - device_id: Arc, + device_id: Arc>, account_identity_keys: Arc, session_id: Arc, room_id: Arc, @@ -904,7 +904,11 @@ impl OutboundGroupSession { /// session. /// /// * `room_id` - The id of the room that the session is used in. - fn new(device_id: Arc, identity_keys: Arc, room_id: &RoomId) -> Self { + fn new( + device_id: Arc>, + identity_keys: Arc, + room_id: &RoomId, + ) -> Self { let session = OlmOutboundGroupSession::new(); let session_id = session.session_id(); @@ -966,12 +970,14 @@ impl OutboundGroupSession { let ciphertext = self.encrypt_helper(plaintext).await; - EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content { - ciphertext, - sender_key: self.account_identity_keys.curve25519().to_owned(), - session_id: self.session_id().to_owned(), - device_id: (&*self.device_id).to_owned(), - }) + EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content::new( + matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit { + ciphertext, + sender_key: self.account_identity_keys.curve25519().to_owned(), + session_id: self.session_id().to_owned(), + device_id: (&*self.device_id).to_owned(), + }, + )) } /// Check if the session has expired and if it should be rotated. @@ -1044,16 +1050,16 @@ pub(crate) mod test { UserId::try_from("@alice:example.org").unwrap() } - fn alice_device_id() -> DeviceId { - "ALICEDEVICE".to_string() + fn alice_device_id() -> Box { + "ALICEDEVICE".into() } fn bob_id() -> UserId { UserId::try_from("@bob:example.org").unwrap() } - fn bob_device_id() -> DeviceId { - "BOBDEVICE".to_string() + fn bob_device_id() -> Box { + "BOBDEVICE".into() } pub(crate) async fn get_account_and_session() -> (Account, Session) { diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index cfda0a62..44feb951 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -469,14 +469,14 @@ impl SqliteStore { let key = &row.1; keys.insert( - AlgorithmAndDeviceId(algorithm, device_id.clone()), + AlgorithmAndDeviceId(algorithm, device_id.as_str().into()), key.to_owned(), ); } let device = Device::new( user_id, - device_id.to_owned(), + device_id.as_str().into(), display_name.clone(), trust_state, algorithms, @@ -840,16 +840,16 @@ mod test { UserId::try_from("@alice:example.org").unwrap() } - fn alice_device_id() -> DeviceId { - "ALICEDEVICE".to_string() + fn alice_device_id() -> Box { + "ALICEDEVICE".into() } fn bob_id() -> UserId { UserId::try_from("@bob:example.org").unwrap() } - fn bob_device_id() -> DeviceId { - "BOBDEVICE".to_string() + fn bob_device_id() -> Box { + "BOBDEVICE".into() } fn get_account() -> Account { From e4f94cbfec9b1212d5f9ea5c774c14bdc3d4b4f3 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 17 Jul 2020 21:06:51 -0400 Subject: [PATCH 67/83] Remove FullOrRedacted use ruma::AnyPossiblyRedacted event enum --- .pre-commit-config.yaml | 24 +++--- matrix_sdk/src/lib.rs | 2 +- matrix_sdk_base/src/lib.rs | 2 +- matrix_sdk_base/src/models/message.rs | 47 +++++------ matrix_sdk_base/src/models/mod.rs | 2 +- matrix_sdk_base/src/models/room.rs | 107 ++++++-------------------- 6 files changed, 62 insertions(+), 122 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ca778bc..afdeace7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,16 +14,16 @@ repos: types: [file, rust] entry: cargo fmt -- --check - # - id: clippy - # name: clippy - # language: system - # types: [file, rust] - # entry: cargo clippy --all-targets --all - # pass_filenames: false + - id: clippy + name: clippy + language: system + types: [file, rust] + entry: cargo clippy --all-targets --all + pass_filenames: false - # - id: test - # name: test - # language: system - # files: '\.rs$' - # entry: cargo test --lib - # pass_filenames: false + - id: test + name: test + language: system + files: '\.rs$' + entry: cargo test --lib + pass_filenames: false diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 809b8e20..9798c67e 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -41,7 +41,7 @@ pub use matrix_sdk_base::Error as BaseError; pub use matrix_sdk_base::JsonStore; pub use matrix_sdk_base::{CustomOrRawEvent, EventEmitter, Room, Session, SyncRoom}; #[cfg(feature = "messages")] -pub use matrix_sdk_base::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; +pub use matrix_sdk_base::{MessageQueue, MessageWrapper, PossiblyRedactedExt}; pub use matrix_sdk_base::{RoomState, StateStore}; pub use matrix_sdk_common::*; pub use reqwest::header::InvalidHeaderValue; diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index ab7f1e77..dc565c34 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -55,7 +55,7 @@ pub use matrix_sdk_crypto::{Device, TrustState}; #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] -pub use models::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; +pub use models::{MessageQueue, MessageWrapper, PossiblyRedactedExt}; #[cfg(not(target_arch = "wasm32"))] pub use state::JsonStore; diff --git a/matrix_sdk_base/src/models/message.rs b/matrix_sdk_base/src/models/message.rs index 5458a16c..fa47b899 100644 --- a/matrix_sdk_base/src/models/message.rs +++ b/matrix_sdk_base/src/models/message.rs @@ -11,34 +11,34 @@ use std::{ }; use matrix_sdk_common::identifiers::EventId; -use serde::{de, ser, Deserialize, Serialize}; +use serde::{de, ser, Serialize}; -use crate::events::{AnyRedactedSyncMessageEvent, AnySyncMessageEvent}; +use crate::events::AnyPossiblyRedactedSyncMessageEvent; -/// Represents either a redacted event or a non-redacted event. +/// Exposes some of the field access methods found in the event held by +/// `AnyPossiblyRedacted*` enums. /// -/// Note: ruma may create types that solve this. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum FullOrRedactedEvent { - /// A non-redacted event. - Full(AnySyncMessageEvent), - /// An event that has been redacted. - Redacted(AnyRedactedSyncMessageEvent), +/// This is just an extension trait to aid the ease of use of certain event enums. +pub trait PossiblyRedactedExt { + /// Access the redacted or full events `event_id` field. + fn event_id(&self) -> &EventId; + /// Access the redacted or full events `origin_server_ts` field. + fn origin_server_ts(&self) -> &SystemTime; } -impl FullOrRedactedEvent { +impl PossiblyRedactedExt for AnyPossiblyRedactedSyncMessageEvent { /// Access the underlying events `event_id`. - pub fn event_id(&self) -> &EventId { + fn event_id(&self) -> &EventId { match self { - Self::Full(e) => e.event_id(), + Self::Regular(e) => e.event_id(), Self::Redacted(e) => e.event_id(), } } /// Access the underlying events `origin_server_ts`. - pub fn origin_server_ts(&self) -> &SystemTime { + fn origin_server_ts(&self) -> &SystemTime { match self { - Self::Full(e) => e.origin_server_ts(), + Self::Regular(e) => e.origin_server_ts(), Self::Redacted(e) => e.origin_server_ts(), } } @@ -46,7 +46,7 @@ impl FullOrRedactedEvent { const MESSAGE_QUEUE_CAP: usize = 35; -pub type SyncMessageEvent = FullOrRedactedEvent; +pub type SyncMessageEvent = AnyPossiblyRedactedSyncMessageEvent; /// A queue that holds the 35 most recent messages received from the server. #[derive(Clone, Debug, Default)] @@ -206,17 +206,18 @@ pub(crate) mod ser_deser { #[cfg(test)] mod test { - use super::*; - use std::collections::HashMap; use std::convert::TryFrom; + use matrix_sdk_common::{ + events::{AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent}, + identifiers::{RoomId, UserId}, + }; + use matrix_sdk_test::test_json; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; - use matrix_sdk_test::test_json; - - use crate::identifiers::{RoomId, UserId}; + use super::*; use crate::Room; #[test] @@ -227,7 +228,7 @@ mod test { let mut room = Room::new(&id, &user); let json: &serde_json::Value = &test_json::MESSAGE_TEXT; - let msg = FullOrRedactedEvent::Full( + let msg = AnyPossiblyRedactedSyncMessageEvent::Regular( serde_json::from_value::(json.clone()).unwrap(), ); @@ -274,7 +275,7 @@ mod test { let mut room = Room::new(&id, &user); let json: &serde_json::Value = &test_json::MESSAGE_TEXT; - let msg = FullOrRedactedEvent::Full( + let msg = AnyPossiblyRedactedSyncMessageEvent::Regular( serde_json::from_value::(json.clone()).unwrap(), ); diff --git a/matrix_sdk_base/src/models/mod.rs b/matrix_sdk_base/src/models/mod.rs index 4cf49a20..20514724 100644 --- a/matrix_sdk_base/src/models/mod.rs +++ b/matrix_sdk_base/src/models/mod.rs @@ -6,6 +6,6 @@ mod room_member; #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] -pub use message::{FullOrRedactedEvent, MessageQueue, MessageWrapper}; +pub use message::{MessageQueue, MessageWrapper, PossiblyRedactedExt}; pub use room::{Room, RoomName}; pub use room_member::RoomMember; diff --git a/matrix_sdk_base/src/models/room.rs b/matrix_sdk_base/src/models/room.rs index 1590042b..7442d412 100644 --- a/matrix_sdk_base/src/models/room.rs +++ b/matrix_sdk_base/src/models/room.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, error, trace}; #[cfg(feature = "messages")] -use super::message::{FullOrRedactedEvent, MessageQueue}; +use super::message::MessageQueue; use super::RoomMember; use crate::api::r0::sync::sync_events::{RoomSummary, UnreadNotificationsCount}; use crate::events::{ @@ -42,30 +42,13 @@ use crate::events::{ #[cfg(feature = "messages")] use crate::events::{ - room::redaction::{RedactionEvent, SyncRedactionEvent}, - AnySyncMessageEvent, + room::redaction::SyncRedactionEvent, AnyPossiblyRedactedSyncMessageEvent, AnySyncMessageEvent, }; use crate::identifiers::{RoomAliasId, RoomId, UserId}; use crate::js_int::{int, uint, Int, UInt}; -#[cfg(feature = "messages")] -fn redaction_event_from_redaction_stub( - event: SyncRedactionEvent, - room_id: RoomId, -) -> RedactionEvent { - RedactionEvent { - content: event.content, - redacts: event.redacts, - event_id: event.event_id, - unsigned: event.unsigned, - sender: event.sender, - origin_server_ts: event.origin_server_ts, - room_id, - } -} - #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] /// `RoomName` allows the calculation of a text room name. pub struct RoomName { @@ -674,7 +657,8 @@ impl Room { #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] pub fn handle_message(&mut self, event: &AnySyncMessageEvent) -> bool { - self.messages.push(FullOrRedactedEvent::Full(event.clone())) + self.messages + .push(AnyPossiblyRedactedSyncMessageEvent::Regular(event.clone())) } /// Handle a room.redaction event and update the `MessageQueue`. @@ -686,68 +670,23 @@ impl Room { #[cfg(feature = "messages")] #[cfg_attr(docsrs, doc(cfg(feature = "messages")))] pub fn handle_redaction(&mut self, redacted_event: &SyncRedactionEvent) -> bool { - use matrix_sdk_common::events::{ - AnyRedactedSyncMessageEvent, EventJson, RedactedSyncMessageEvent, SyncMessageEvent, - }; + use crate::identifiers::RoomVersionId; + use crate::models::message::PossiblyRedactedExt; + if let Some(msg) = self .messages .iter_mut() .find(|msg| &redacted_event.redacts == msg.event_id()) { match msg.deref_mut() { - FullOrRedactedEvent::Full(event) => match event { - AnySyncMessageEvent::RoomMessage(event) => { - let SyncMessageEvent { - content, - event_id, - sender, - origin_server_ts, - mut unsigned, - } = event.clone(); - unsigned.redacted_because = - Some(EventJson::from(redaction_event_from_redaction_stub( - redacted_event.clone(), - self.room_id.clone(), - ))); - let redacted = content.redact(); - msg.0 = FullOrRedactedEvent::Redacted( - AnyRedactedSyncMessageEvent::RoomMessage(RedactedSyncMessageEvent { - content: redacted, - event_id, - origin_server_ts, - sender, - unsigned, - }), - ) - } - AnySyncMessageEvent::Sticker(event) => { - let SyncMessageEvent { - content, - event_id, - sender, - origin_server_ts, - mut unsigned, - } = event.clone(); - unsigned.redacted_because = - Some(EventJson::from(redaction_event_from_redaction_stub( - redacted_event.clone(), - self.room_id.clone(), - ))); - let redacted = content.redact(); - msg.0 = FullOrRedactedEvent::Redacted(AnyRedactedSyncMessageEvent::Sticker( - RedactedSyncMessageEvent { - content: redacted, - event_id, - origin_server_ts, - sender, - unsigned, - }, - )) - } - // TODO handle the rest of the message events - _ => {} - }, - FullOrRedactedEvent::Redacted(_) => return false, + AnyPossiblyRedactedSyncMessageEvent::Regular(event) => { + msg.0 = AnyPossiblyRedactedSyncMessageEvent::Redacted( + event + .clone() + .redact(redacted_event.clone(), RoomVersionId::version_6()), + ); + } + AnyPossiblyRedactedSyncMessageEvent::Redacted(_) => return false, } true } else { @@ -1141,7 +1080,7 @@ impl Describe for MembershipChange { #[cfg(test)] mod test { use super::*; - use crate::events::{room::encryption::EncryptionEventContent, EventJson, UnsignedData}; + use crate::events::{room::encryption::EncryptionEventContent, EventJson, Unsigned}; use crate::identifiers::{EventId, UserId}; use crate::{BaseClient, Session}; use matrix_sdk_test::{async_test, sync_response, EventBuilder, EventsJson, SyncResponseFile}; @@ -1714,7 +1653,7 @@ mod test { for room in client.joined_rooms().read().await.values() { let queue = &room.read().await.messages; - if let crate::models::message::FullOrRedactedEvent::Redacted( + if let crate::events::AnyPossiblyRedactedSyncMessageEvent::Redacted( crate::events::AnyRedactedSyncMessageEvent::RoomMessage(event), ) = &queue.msgs[0].deref() { @@ -1746,17 +1685,17 @@ mod test { client.restore_login(session).await.unwrap(); client.receive_sync_response(&mut response).await.unwrap(); + let mut content = EncryptionEventContent::new(Algorithm::MegolmV1AesSha2); + content.rotation_period_ms = Some(100_000u32.into()); + content.rotation_period_msgs = Some(100u32.into()); + let event = SyncStateEvent { event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), origin_server_ts: SystemTime::now(), sender: user_id, state_key: "".into(), - unsigned: UnsignedData::default(), - content: EncryptionEventContent { - algorithm: Algorithm::MegolmV1AesSha2, - rotation_period_ms: Some(100_000u32.into()), - rotation_period_msgs: Some(100u32.into()), - }, + unsigned: Unsigned::default(), + content, prev_content: None, }; From e27b6fb51e20926c9e60111ecdbff23594ae90b8 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 17 Jul 2020 15:43:06 -0400 Subject: [PATCH 68/83] matrix-sdk-crypto: Fix map_clone clippy warning --- matrix_sdk_crypto/src/memory_stores.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 51303864..67fe9245 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -199,6 +199,7 @@ impl DeviceStore { } /// Get a read-only view over all devices of the given user. + #[allow(clippy::map_clone)] pub fn user_devices(&self, user_id: &UserId) -> UserDevices { if !self.entries.contains_key(user_id) { self.entries.insert(user_id.clone(), DashMap::new()); @@ -207,7 +208,7 @@ impl DeviceStore { entries: self .entries .get(user_id) - .map(|d| d.clone()) // TODO I'm sure this is not ok but I'm not sure what to do?? + .map(|map| map.clone()) // TODO I'm sure this is not ok but I'm not sure what to do?? .unwrap() .into_read_only(), } From 8c39db002b4e2fefe40fa5c426ff28c70b87337d Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 17 Jul 2020 21:11:23 -0400 Subject: [PATCH 69/83] Remove inaccurate comment about DeviceId --- matrix_sdk_crypto/src/memory_stores.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 67fe9245..7389eb2f 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -175,8 +175,6 @@ impl DeviceStore { let device_map = self.entries.get_mut(&user_id).unwrap(); device_map - // TODO this is ok if this is for sure a valid device_id otherwise - // Box::::try_from(&str) is the validated version .insert(device.device_id().into(), device) .is_none() } From 037d62b165706ebd45b3e134ca5d0ed196a271a3 Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 20 Jul 2020 08:10:42 -0400 Subject: [PATCH 70/83] matrix-sdk-crypto: Remove map clone from user_devices --- matrix_sdk_crypto/src/memory_stores.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 7389eb2f..7cf6351c 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -197,18 +197,12 @@ impl DeviceStore { } /// Get a read-only view over all devices of the given user. - #[allow(clippy::map_clone)] pub fn user_devices(&self, user_id: &UserId) -> UserDevices { if !self.entries.contains_key(user_id) { self.entries.insert(user_id.clone(), DashMap::new()); } UserDevices { - entries: self - .entries - .get(user_id) - .map(|map| map.clone()) // TODO I'm sure this is not ok but I'm not sure what to do?? - .unwrap() - .into_read_only(), + entries: self.entries.get(user_id).unwrap().clone().into_read_only(), } } } From b22324b305fd77dd6d6e3e0efb2d670e0e2952ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 10:38:14 +0200 Subject: [PATCH 71/83] crypto: Split out the olm module into separate files. --- matrix_sdk_crypto/src/olm.rs | 1225 ------------------- matrix_sdk_crypto/src/olm/account.rs | 544 ++++++++ matrix_sdk_crypto/src/olm/group_sessions.rs | 400 ++++++ matrix_sdk_crypto/src/olm/mod.rs | 208 ++++ matrix_sdk_crypto/src/olm/session.rs | 161 +++ 5 files changed, 1313 insertions(+), 1225 deletions(-) delete mode 100644 matrix_sdk_crypto/src/olm.rs create mode 100644 matrix_sdk_crypto/src/olm/account.rs create mode 100644 matrix_sdk_crypto/src/olm/group_sessions.rs create mode 100644 matrix_sdk_crypto/src/olm/mod.rs create mode 100644 matrix_sdk_crypto/src/olm/session.rs diff --git a/matrix_sdk_crypto/src/olm.rs b/matrix_sdk_crypto/src/olm.rs deleted file mode 100644 index 106445c6..00000000 --- a/matrix_sdk_crypto/src/olm.rs +++ /dev/null @@ -1,1225 +0,0 @@ -// Copyright 2020 The Matrix.org Foundation C.I.C. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use matrix_sdk_common::instant::Instant; -use std::convert::TryFrom; -use std::convert::TryInto; -use std::fmt; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicUsize, Ordering}; -use std::sync::Arc; - -use matrix_sdk_common::locks::Mutex; -use serde::Serialize; -use serde_json::{json, Value}; -use std::collections::BTreeMap; -use zeroize::Zeroize; - -pub use olm_rs::account::IdentityKeys; -use olm_rs::account::{OlmAccount, OneTimeKeys}; -use olm_rs::errors::{OlmAccountError, OlmGroupSessionError, OlmSessionError}; -use olm_rs::inbound_group_session::OlmInboundGroupSession; -use olm_rs::outbound_group_session::OlmOutboundGroupSession; -use olm_rs::session::OlmSession; -use olm_rs::PicklingMode; - -use crate::device::Device; -use crate::error::{EventError, MegolmResult, SessionCreationError}; -pub use olm_rs::{ - session::{OlmMessage, PreKeyMessage}, - utility::OlmUtility, -}; - -use matrix_sdk_common::{ - api::r0::keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey}, - events::{ - room::{ - encrypted::{EncryptedEventContent, MegolmV1AesSha2Content}, - message::MessageEventContent, - }, - Algorithm, AnySyncRoomEvent, EventJson, EventType, SyncMessageEvent, - }, - identifiers::{DeviceId, RoomId, UserId}, -}; - -/// Account holding identity keys for which sessions can be created. -/// -/// An account is the central identity for encrypted communication between two -/// devices. -#[derive(Clone)] -pub struct Account { - user_id: Arc, - device_id: Arc>, - inner: Arc>, - identity_keys: Arc, - shared: Arc, - /// The number of signed one-time keys we have uploaded to the server. If - /// this is None, no action will be taken. After a sync request the client - /// needs to set this for us, depending on the count we will suggest the - /// client to upload new keys. - uploaded_signed_key_count: Arc, -} - -// #[cfg_attr(tarpaulin, skip)] -impl fmt::Debug for Account { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Account") - .field("identity_keys", self.identity_keys()) - .field("shared", &self.shared()) - .finish() - } -} - -impl Account { - const ALGORITHMS: &'static [&'static Algorithm] = &[ - &Algorithm::OlmV1Curve25519AesSha2, - &Algorithm::MegolmV1AesSha2, - ]; - - /// Create a fresh new account, this will generate the identity key-pair. - #[allow(clippy::ptr_arg)] - pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self { - let account = OlmAccount::new(); - let identity_keys = account.parsed_identity_keys(); - - Account { - user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.into()), - inner: Arc::new(Mutex::new(account)), - identity_keys: Arc::new(identity_keys), - shared: Arc::new(AtomicBool::new(false)), - uploaded_signed_key_count: Arc::new(AtomicI64::new(0)), - } - } - - /// Get the public parts of the identity keys for the account. - pub fn identity_keys(&self) -> &IdentityKeys { - &self.identity_keys - } - - /// Update the uploaded key count. - /// - /// # Arguments - /// - /// * `new_count` - The new count that was reported by the server. - pub(crate) fn update_uploaded_key_count(&self, new_count: u64) { - let key_count = i64::try_from(new_count).unwrap_or(i64::MAX); - self.uploaded_signed_key_count - .store(key_count, Ordering::Relaxed); - } - - /// Get the currently known uploaded key count. - pub fn uploaded_key_count(&self) -> i64 { - self.uploaded_signed_key_count.load(Ordering::Relaxed) - } - - /// Has the account been shared with the server. - pub fn shared(&self) -> bool { - self.shared.load(Ordering::Relaxed) - } - - /// Mark the account as shared. - /// - /// Messages shouldn't be encrypted with the session before it has been - /// shared. - pub(crate) fn mark_as_shared(&self) { - self.shared.store(true, Ordering::Relaxed); - } - - /// Get the one-time keys of the account. - /// - /// This can be empty, keys need to be generated first. - pub(crate) async fn one_time_keys(&self) -> OneTimeKeys { - self.inner.lock().await.parsed_one_time_keys() - } - - /// Generate count number of one-time keys. - pub(crate) async fn generate_one_time_keys_helper(&self, count: usize) { - self.inner.lock().await.generate_one_time_keys(count); - } - - /// Get the maximum number of one-time keys the account can hold. - pub(crate) async fn max_one_time_keys(&self) -> usize { - self.inner.lock().await.max_number_of_one_time_keys() - } - - /// Get a tuple of device and one-time keys that need to be uploaded. - /// - /// Returns an empty error if no keys need to be uploaded. - pub(crate) async fn generate_one_time_keys(&self) -> Result { - let count = self.uploaded_key_count() as u64; - let max_keys = self.max_one_time_keys().await; - let max_on_server = (max_keys as u64) / 2; - - if count >= (max_on_server) { - return Err(()); - } - - let key_count = (max_on_server) - count; - let key_count: usize = key_count.try_into().unwrap_or(max_keys); - - self.generate_one_time_keys_helper(key_count).await; - Ok(key_count as u64) - } - - /// Should account or one-time keys be uploaded to the server. - pub(crate) async fn should_upload_keys(&self) -> bool { - if !self.shared() { - return true; - } - - let count = self.uploaded_key_count() as u64; - - // If we have a known key count, check that we have more than - // max_one_time_Keys() / 2, otherwise tell the client to upload more. - let max_keys = self.max_one_time_keys().await as u64; - // If there are more keys already uploaded than max_key / 2 - // bail out returning false, this also avoids overflow. - if count > (max_keys / 2) { - return false; - } - - let key_count = (max_keys / 2) - count; - key_count > 0 - } - - /// Get a tuple of device and one-time keys that need to be uploaded. - /// - /// Returns an empty error if no keys need to be uploaded. - pub(crate) async fn keys_for_upload( - &self, - ) -> Result< - ( - Option, - Option>, - ), - (), - > { - if !self.should_upload_keys().await { - return Err(()); - } - - let device_keys = if !self.shared() { - Some(self.device_keys().await) - } else { - None - }; - - let one_time_keys = self.signed_one_time_keys().await.ok(); - - Ok((device_keys, one_time_keys)) - } - - /// Mark the current set of one-time keys as being published. - pub(crate) async fn mark_keys_as_published(&self) { - self.inner.lock().await.mark_keys_as_published(); - } - - /// Sign the given string using the accounts signing key. - /// - /// Returns the signature as a base64 encoded string. - pub async fn sign(&self, string: &str) -> String { - self.inner.lock().await.sign(string) - } - - /// Store the account as a base64 encoded string. - /// - /// # Arguments - /// - /// * `pickle_mode` - The mode that was used to pickle the account, either an - /// unencrypted mode or an encrypted using passphrase. - pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { - self.inner.lock().await.pickle(pickle_mode) - } - - /// Restore an account from a previously pickled string. - /// - /// # Arguments - /// - /// * `pickle` - The pickled string of the account. - /// - /// * `pickle_mode` - The mode that was used to pickle the account, either an - /// unencrypted mode or an encrypted using passphrase. - /// - /// * `shared` - Boolean determining if the account was uploaded to the - /// server. - #[allow(clippy::ptr_arg)] - pub fn from_pickle( - pickle: String, - pickle_mode: PicklingMode, - shared: bool, - uploaded_signed_key_count: i64, - user_id: &UserId, - device_id: &DeviceId, - ) -> Result { - let account = OlmAccount::unpickle(pickle, pickle_mode)?; - let identity_keys = account.parsed_identity_keys(); - - Ok(Account { - user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.into()), - inner: Arc::new(Mutex::new(account)), - identity_keys: Arc::new(identity_keys), - shared: Arc::new(AtomicBool::from(shared)), - uploaded_signed_key_count: Arc::new(AtomicI64::new(uploaded_signed_key_count)), - }) - } - - /// Sign the device keys of the account and return them so they can be - /// uploaded. - pub(crate) async fn device_keys(&self) -> DeviceKeys { - let identity_keys = self.identity_keys(); - - let mut keys = BTreeMap::new(); - - keys.insert( - AlgorithmAndDeviceId(KeyAlgorithm::Curve25519, (*self.device_id).clone()), - identity_keys.curve25519().to_owned(), - ); - keys.insert( - AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), - identity_keys.ed25519().to_owned(), - ); - - let device_keys = json!({ - "user_id": (*self.user_id).clone(), - "device_id": (*self.device_id).clone(), - "algorithms": Account::ALGORITHMS, - "keys": keys, - }); - - let mut signatures = BTreeMap::new(); - - let mut signature = BTreeMap::new(); - signature.insert( - AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), - self.sign_json(&device_keys).await, - ); - signatures.insert((*self.user_id).clone(), signature); - - DeviceKeys { - user_id: (*self.user_id).clone(), - device_id: (*self.device_id).clone(), - algorithms: vec![ - Algorithm::OlmV1Curve25519AesSha2, - Algorithm::MegolmV1AesSha2, - ], - keys, - signatures, - unsigned: None, - } - } - - /// Convert a JSON value to the canonical representation and sign the JSON - /// string. - /// - /// # Arguments - /// - /// * `json` - The value that should be converted into a canonical JSON - /// string. - /// - /// # Panic - /// - /// Panics if the json value can't be serialized. - pub async fn sign_json(&self, json: &Value) -> String { - let canonical_json = cjson::to_string(json) - .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", json))); - self.sign(&canonical_json).await - } - - /// Generate, sign and prepare one-time keys to be uploaded. - /// - /// If no one-time keys need to be uploaded returns an empty error. - pub(crate) async fn signed_one_time_keys( - &self, - ) -> Result, ()> { - let _ = self.generate_one_time_keys().await?; - - let one_time_keys = self.one_time_keys().await; - let mut one_time_key_map = BTreeMap::new(); - - for (key_id, key) in one_time_keys.curve25519().iter() { - let key_json = json!({ - "key": key, - }); - - let signature = self.sign_json(&key_json).await; - - let mut signature_map = BTreeMap::new(); - - signature_map.insert( - AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), - signature, - ); - - let mut signatures = BTreeMap::new(); - signatures.insert((*self.user_id).clone(), signature_map); - - let signed_key = SignedKey { - key: key.to_owned(), - signatures, - }; - - one_time_key_map.insert( - AlgorithmAndDeviceId(KeyAlgorithm::SignedCurve25519, key_id.as_str().into()), - OneTimeKey::SignedKey(signed_key), - ); - } - - Ok(one_time_key_map) - } - - /// Create a new session with another account given a one-time key. - /// - /// Returns the newly created session or a `OlmSessionError` if creating a - /// session failed. - /// - /// # Arguments - /// * `their_identity_key` - The other account's identity/curve25519 key. - /// - /// * `their_one_time_key` - A signed one-time key that the other account - /// created and shared with us. - pub(crate) async fn create_outbound_session_helper( - &self, - their_identity_key: &str, - their_one_time_key: &SignedKey, - ) -> Result { - let session = self - .inner - .lock() - .await - .create_outbound_session(their_identity_key, &their_one_time_key.key)?; - - let now = Instant::now(); - let session_id = session.session_id(); - - Ok(Session { - inner: Arc::new(Mutex::new(session)), - session_id: Arc::new(session_id), - sender_key: Arc::new(their_identity_key.to_owned()), - creation_time: Arc::new(now), - last_use_time: Arc::new(now), - }) - } - - /// Create a new session with another account given a one-time key and a - /// device. - /// - /// Returns the newly created session or a `OlmSessionError` if creating a - /// session failed. - /// - /// # Arguments - /// * `device` - The other account's device. - /// - /// * `key_map` - A map from the algorithm and device id to the one-time - /// key that the other account created and shared with us. - pub(crate) async fn create_outbound_session( - &self, - device: Device, - key_map: &BTreeMap, - ) -> Result { - let one_time_key = key_map.values().next().ok_or_else(|| { - SessionCreationError::OneTimeKeyMissing( - device.user_id().to_owned(), - device.device_id().into(), - ) - })?; - - let one_time_key = match one_time_key { - OneTimeKey::SignedKey(k) => k, - OneTimeKey::Key(_) => { - return Err(SessionCreationError::OneTimeKeyNotSigned( - device.user_id().to_owned(), - device.device_id().into(), - )); - } - }; - - device.verify_one_time_key(&one_time_key).map_err(|e| { - SessionCreationError::InvalidSignature( - device.user_id().to_owned(), - device.device_id().into(), - e, - ) - })?; - - let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or_else(|| { - SessionCreationError::DeviceMissingCurveKey( - device.user_id().to_owned(), - device.device_id().into(), - ) - })?; - - self.create_outbound_session_helper(curve_key, &one_time_key) - .await - .map_err(|e| { - SessionCreationError::OlmError( - device.user_id().to_owned(), - device.device_id().into(), - e, - ) - }) - } - - /// Create a new session with another account given a pre-key Olm message. - /// - /// Returns the newly created session or a `OlmSessionError` if creating a - /// session failed. - /// - /// # Arguments - /// * `their_identity_key` - The other account's identitiy/curve25519 key. - /// - /// * `message` - A pre-key Olm message that was sent to us by the other - /// account. - pub(crate) async fn create_inbound_session( - &self, - their_identity_key: &str, - message: PreKeyMessage, - ) -> Result { - let session = self - .inner - .lock() - .await - .create_inbound_session_from(their_identity_key, message)?; - - self.inner - .lock() - .await - .remove_one_time_keys(&session) - .expect( - "Session was successfully created but the account doesn't hold a matching one-time key", - ); - - let now = Instant::now(); - let session_id = session.session_id(); - - Ok(Session { - inner: Arc::new(Mutex::new(session)), - session_id: Arc::new(session_id), - sender_key: Arc::new(their_identity_key.to_owned()), - creation_time: Arc::new(now), - last_use_time: Arc::new(now), - }) - } - - /// Create a group session pair. - /// - /// This session pair can be used to encrypt and decrypt messages meant for - /// a large group of participants. - /// - /// The outbound session is used to encrypt messages while the inbound one - /// is used to decrypt messages encrypted by the outbound one. - /// - /// # Arguments - /// - /// * `room_id` - The ID of the room where the group session will be used. - pub(crate) async fn create_group_session_pair( - &self, - room_id: &RoomId, - ) -> (OutboundGroupSession, InboundGroupSession) { - let outbound = - OutboundGroupSession::new(self.device_id.clone(), self.identity_keys.clone(), room_id); - let identity_keys = self.identity_keys(); - - let sender_key = identity_keys.curve25519(); - let signing_key = identity_keys.ed25519(); - - let inbound = InboundGroupSession::new( - sender_key, - signing_key, - &room_id, - outbound.session_key().await, - ) - .expect("Can't create inbound group session from a newly created outbound group session"); - - (outbound, inbound) - } -} - -impl PartialEq for Account { - fn eq(&self, other: &Self) -> bool { - self.identity_keys() == other.identity_keys() && self.shared() == other.shared() - } -} - -/// Cryptographic session that enables secure communication between two -/// `Account`s -#[derive(Clone)] -pub struct Session { - inner: Arc>, - session_id: Arc, - pub(crate) sender_key: Arc, - pub(crate) creation_time: Arc, - pub(crate) last_use_time: Arc, -} - -// #[cfg_attr(tarpaulin, skip)] -impl fmt::Debug for Session { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Session") - .field("session_id", &self.session_id()) - .field("sender_key", &self.sender_key) - .finish() - } -} - -impl Session { - /// Decrypt the given Olm message. - /// - /// Returns the decrypted plaintext or an `OlmSessionError` if decryption - /// failed. - /// - /// # Arguments - /// - /// * `message` - The Olm message that should be decrypted. - pub async fn decrypt(&mut self, message: OlmMessage) -> Result { - let plaintext = self.inner.lock().await.decrypt(message)?; - self.last_use_time = Arc::new(Instant::now()); - Ok(plaintext) - } - - /// Encrypt the given plaintext as a OlmMessage. - /// - /// Returns the encrypted Olm message. - /// - /// # Arguments - /// - /// * `plaintext` - The plaintext that should be encrypted. - pub async fn encrypt(&mut self, plaintext: &str) -> OlmMessage { - let message = self.inner.lock().await.encrypt(plaintext); - self.last_use_time = Arc::new(Instant::now()); - message - } - - /// Check if a pre-key Olm message was encrypted for this session. - /// - /// Returns true if it matches, false if not and a OlmSessionError if there - /// was an error checking if it matches. - /// - /// # Arguments - /// - /// * `their_identity_key` - The identity/curve25519 key of the account - /// that encrypted this Olm message. - /// - /// * `message` - The pre-key Olm message that should be checked. - pub async fn matches( - &self, - their_identity_key: &str, - message: PreKeyMessage, - ) -> Result { - self.inner - .lock() - .await - .matches_inbound_session_from(their_identity_key, message) - } - - /// Returns the unique identifier for this session. - pub fn session_id(&self) -> &str { - &self.session_id - } - - /// Store the session as a base64 encoded string. - /// - /// # Arguments - /// - /// * `pickle_mode` - The mode that was used to pickle the session, either - /// an unencrypted mode or an encrypted using passphrase. - pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { - self.inner.lock().await.pickle(pickle_mode) - } - - /// Restore a Session from a previously pickled string. - /// - /// Returns the restored Olm Session or a `OlmSessionError` if there was an - /// error. - /// - /// # Arguments - /// - /// * `pickle` - The pickled string of the session. - /// - /// * `pickle_mode` - The mode that was used to pickle the session, either - /// an unencrypted mode or an encrypted using passphrase. - /// - /// * `sender_key` - The public curve25519 key of the account that - /// established the session with us. - /// - /// * `creation_time` - The timestamp that marks when the session was - /// created. - /// - /// * `last_use_time` - The timestamp that marks when the session was - /// last used to encrypt or decrypt an Olm message. - pub fn from_pickle( - pickle: String, - pickle_mode: PicklingMode, - sender_key: String, - creation_time: Instant, - last_use_time: Instant, - ) -> Result { - let session = OlmSession::unpickle(pickle, pickle_mode)?; - let session_id = session.session_id(); - - Ok(Session { - inner: Arc::new(Mutex::new(session)), - session_id: Arc::new(session_id), - sender_key: Arc::new(sender_key), - creation_time: Arc::new(creation_time), - last_use_time: Arc::new(last_use_time), - }) - } -} - -impl PartialEq for Session { - fn eq(&self, other: &Self) -> bool { - self.session_id() == other.session_id() - } -} - -/// The private session key of a group session. -/// Can be used to create a new inbound group session. -#[derive(Clone, Debug, Serialize, Zeroize)] -#[zeroize(drop)] -pub struct GroupSessionKey(pub String); - -/// Inbound group session. -/// -/// Inbound group sessions are used to exchange room messages between a group of -/// participants. Inbound group sessions are used to decrypt the room messages. -#[derive(Clone)] -pub struct InboundGroupSession { - inner: Arc>, - session_id: Arc, - pub(crate) sender_key: Arc, - pub(crate) signing_key: Arc, - pub(crate) room_id: Arc, - forwarding_chains: Arc>>>, -} - -impl InboundGroupSession { - /// Create a new inbound group session for the given room. - /// - /// These sessions are used to decrypt room messages. - /// - /// # Arguments - /// - /// * `sender_key` - The public curve25519 key of the account that - /// sent us the session - /// - /// * `signing_key` - The public ed25519 key of the account that - /// sent us the session. - /// - /// * `room_id` - The id of the room that the session is used in. - /// - /// * `session_key` - The private session key that is used to decrypt - /// messages. - pub fn new( - sender_key: &str, - signing_key: &str, - room_id: &RoomId, - session_key: GroupSessionKey, - ) -> Result { - let session = OlmInboundGroupSession::new(&session_key.0)?; - let session_id = session.session_id(); - - Ok(InboundGroupSession { - inner: Arc::new(Mutex::new(session)), - session_id: Arc::new(session_id), - sender_key: Arc::new(sender_key.to_owned()), - signing_key: Arc::new(signing_key.to_owned()), - room_id: Arc::new(room_id.clone()), - forwarding_chains: Arc::new(Mutex::new(None)), - }) - } - - /// Store the group session as a base64 encoded string. - /// - /// # Arguments - /// - /// * `pickle_mode` - The mode that was used to pickle the group session, - /// either an unencrypted mode or an encrypted using passphrase. - pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { - self.inner.lock().await.pickle(pickle_mode) - } - - /// Restore a Session from a previously pickled string. - /// - /// Returns the restored group session or a `OlmGroupSessionError` if there - /// was an error. - /// - /// # Arguments - /// - /// * `pickle` - The pickled string of the group session session. - /// - /// * `pickle_mode` - The mode that was used to pickle the session, either - /// an unencrypted mode or an encrypted using passphrase. - /// - /// * `sender_key` - The public curve25519 key of the account that - /// sent us the session - /// - /// * `signing_key` - The public ed25519 key of the account that - /// sent us the session. - /// - /// * `room_id` - The id of the room that the session is used in. - pub fn from_pickle( - pickle: String, - pickle_mode: PicklingMode, - sender_key: String, - signing_key: String, - room_id: RoomId, - ) -> Result { - let session = OlmInboundGroupSession::unpickle(pickle, pickle_mode)?; - let session_id = session.session_id(); - - Ok(InboundGroupSession { - inner: Arc::new(Mutex::new(session)), - session_id: Arc::new(session_id), - sender_key: Arc::new(sender_key), - signing_key: Arc::new(signing_key), - room_id: Arc::new(room_id), - forwarding_chains: Arc::new(Mutex::new(None)), - }) - } - - /// Returns the unique identifier for this session. - pub fn session_id(&self) -> &str { - &self.session_id - } - - /// Get the first message index we know how to decrypt. - pub async fn first_known_index(&self) -> u32 { - self.inner.lock().await.first_known_index() - } - - /// Decrypt the given ciphertext. - /// - /// Returns the decrypted plaintext or an `OlmGroupSessionError` if - /// decryption failed. - /// - /// # Arguments - /// - /// * `message` - The message that should be decrypted. - pub async fn decrypt_helper( - &self, - message: String, - ) -> Result<(String, u32), OlmGroupSessionError> { - self.inner.lock().await.decrypt(message) - } - - /// Decrypt an event from a room timeline. - /// - /// # Arguments - /// - /// * `event` - The event that should be decrypted. - pub async fn decrypt( - &self, - event: &SyncMessageEvent, - ) -> MegolmResult<(EventJson, u32)> { - let content = match &event.content { - EncryptedEventContent::MegolmV1AesSha2(c) => c, - _ => return Err(EventError::UnsupportedAlgorithm.into()), - }; - - let (plaintext, message_index) = self.decrypt_helper(content.ciphertext.clone()).await?; - - let mut decrypted_value = serde_json::from_str::(&plaintext)?; - let decrypted_object = decrypted_value - .as_object_mut() - .ok_or(EventError::NotAnObject)?; - - // TODO better number conversion here. - let server_ts = event - .origin_server_ts - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .unwrap_or_default() - .as_millis(); - let server_ts: i64 = server_ts.try_into().unwrap_or_default(); - - decrypted_object.insert("sender".to_owned(), event.sender.to_string().into()); - decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into()); - decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into()); - - decrypted_object.insert( - "unsigned".to_owned(), - serde_json::to_value(&event.unsigned).unwrap_or_default(), - ); - - Ok(( - serde_json::from_value::>(decrypted_value)?, - message_index, - )) - } -} - -// #[cfg_attr(tarpaulin, skip)] -impl fmt::Debug for InboundGroupSession { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InboundGroupSession") - .field("session_id", &self.session_id()) - .finish() - } -} - -impl PartialEq for InboundGroupSession { - fn eq(&self, other: &Self) -> bool { - self.session_id() == other.session_id() - } -} - -/// Outbound group session. -/// -/// Outbound group sessions are used to exchange room messages between a group -/// of participants. Outbound group sessions are used to encrypt the room -/// messages. -#[derive(Clone)] -pub struct OutboundGroupSession { - inner: Arc>, - device_id: Arc>, - account_identity_keys: Arc, - session_id: Arc, - room_id: Arc, - creation_time: Arc, - message_count: Arc, - shared: Arc, -} - -impl OutboundGroupSession { - /// Create a new outbound group session for the given room. - /// - /// Outbound group sessions are used to encrypt room messages. - /// - /// # Arguments - /// - /// * `device_id` - The id of the device that created this session. - /// - /// * `identity_keys` - The identity keys of the account that created this - /// session. - /// - /// * `room_id` - The id of the room that the session is used in. - fn new( - device_id: Arc>, - identity_keys: Arc, - room_id: &RoomId, - ) -> Self { - let session = OlmOutboundGroupSession::new(); - let session_id = session.session_id(); - - OutboundGroupSession { - inner: Arc::new(Mutex::new(session)), - room_id: Arc::new(room_id.to_owned()), - device_id, - account_identity_keys: identity_keys, - session_id: Arc::new(session_id), - creation_time: Arc::new(Instant::now()), - message_count: Arc::new(AtomicUsize::new(0)), - shared: Arc::new(AtomicBool::new(false)), - } - } - - /// Encrypt the given plaintext using this session. - /// - /// Returns the encrypted ciphertext. - /// - /// # Arguments - /// - /// * `plaintext` - The plaintext that should be encrypted. - async fn encrypt_helper(&self, plaintext: String) -> String { - let session = self.inner.lock().await; - session.encrypt(plaintext) - } - - /// Encrypt a room message for the given room. - /// - /// Beware that a group session needs to be shared before this method can be - /// called using the `share_group_session()` method. - /// - /// Since group sessions can expire or become invalid if the room membership - /// changes client authors should check with the - /// `should_share_group_session()` method if a new group session needs to - /// be shared. - /// - /// # Arguments - /// - /// * `content` - The plaintext content of the message that should be - /// encrypted. - /// - /// # Panics - /// - /// Panics if the content can't be serialized. - pub async fn encrypt(&self, content: MessageEventContent) -> EncryptedEventContent { - let json_content = json!({ - "content": content, - "room_id": &*self.room_id, - "type": EventType::RoomMessage, - }); - - let plaintext = cjson::to_string(&json_content).unwrap_or_else(|_| { - panic!(format!( - "Can't serialize {} to canonical JSON", - json_content - )) - }); - - let ciphertext = self.encrypt_helper(plaintext).await; - - EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content::new( - matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit { - ciphertext, - sender_key: self.account_identity_keys.curve25519().to_owned(), - session_id: self.session_id().to_owned(), - device_id: (&*self.device_id).to_owned(), - }, - )) - } - - /// Check if the session has expired and if it should be rotated. - /// - /// A session will expire after some time or if enough messages have been - /// encrypted using it. - pub fn expired(&self) -> bool { - // TODO implement this. - false - } - - /// Mark the session as shared. - /// - /// Messages shouldn't be encrypted with the session before it has been - /// shared. - pub fn mark_as_shared(&self) { - self.shared.store(true, Ordering::Relaxed); - } - - /// Check if the session has been marked as shared. - pub fn shared(&self) -> bool { - self.shared.load(Ordering::Relaxed) - } - - /// Get the session key of this session. - /// - /// A session key can be used to to create an `InboundGroupSession`. - pub async fn session_key(&self) -> GroupSessionKey { - let session = self.inner.lock().await; - GroupSessionKey(session.session_key()) - } - - /// Returns the unique identifier for this session. - pub fn session_id(&self) -> &str { - &self.session_id - } - - /// Get the current message index for this session. - /// - /// Each message is sent with an increasing index. This returns the - /// message index that will be used for the next encrypted message. - pub async fn message_index(&self) -> u32 { - let session = self.inner.lock().await; - session.session_message_index() - } -} - -// #[cfg_attr(tarpaulin, skip)] -impl std::fmt::Debug for OutboundGroupSession { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OutboundGroupSession") - .field("session_id", &self.session_id) - .field("room_id", &self.room_id) - .field("creation_time", &self.creation_time) - .field("message_count", &self.message_count) - .finish() - } -} - -#[cfg(test)] -pub(crate) mod test { - use crate::olm::{Account, InboundGroupSession, Session}; - use matrix_sdk_common::api::r0::keys::SignedKey; - use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; - use olm_rs::session::OlmMessage; - use std::collections::BTreeMap; - use std::convert::TryFrom; - - fn alice_id() -> UserId { - UserId::try_from("@alice:example.org").unwrap() - } - - fn alice_device_id() -> Box { - "ALICEDEVICE".into() - } - - fn bob_id() -> UserId { - UserId::try_from("@bob:example.org").unwrap() - } - - fn bob_device_id() -> Box { - "BOBDEVICE".into() - } - - pub(crate) async fn get_account_and_session() -> (Account, Session) { - let alice = Account::new(&alice_id(), &alice_device_id()); - let bob = Account::new(&bob_id(), &bob_device_id()); - - bob.generate_one_time_keys_helper(1).await; - let one_time_key = bob - .one_time_keys() - .await - .curve25519() - .iter() - .next() - .unwrap() - .1 - .to_owned(); - let one_time_key = SignedKey { - key: one_time_key, - signatures: BTreeMap::new(), - }; - let sender_key = bob.identity_keys().curve25519().to_owned(); - let session = alice - .create_outbound_session_helper(&sender_key, &one_time_key) - .await - .unwrap(); - - (alice, session) - } - - #[test] - fn account_creation() { - let account = Account::new(&alice_id(), &alice_device_id()); - let identyty_keys = account.identity_keys(); - - assert!(!account.shared()); - assert!(!identyty_keys.ed25519().is_empty()); - assert_ne!(identyty_keys.values().len(), 0); - assert_ne!(identyty_keys.keys().len(), 0); - assert_ne!(identyty_keys.iter().len(), 0); - assert!(identyty_keys.contains_key("ed25519")); - assert_eq!( - identyty_keys.ed25519(), - identyty_keys.get("ed25519").unwrap() - ); - assert!(!identyty_keys.curve25519().is_empty()); - - account.mark_as_shared(); - assert!(account.shared()); - } - - #[tokio::test] - async fn one_time_keys_creation() { - let account = Account::new(&alice_id(), &alice_device_id()); - let one_time_keys = account.one_time_keys().await; - - assert!(one_time_keys.curve25519().is_empty()); - assert_ne!(account.max_one_time_keys().await, 0); - - account.generate_one_time_keys_helper(10).await; - let one_time_keys = account.one_time_keys().await; - - assert!(!one_time_keys.curve25519().is_empty()); - assert_ne!(one_time_keys.values().len(), 0); - assert_ne!(one_time_keys.keys().len(), 0); - assert_ne!(one_time_keys.iter().len(), 0); - assert!(one_time_keys.contains_key("curve25519")); - assert_eq!(one_time_keys.curve25519().keys().len(), 10); - assert_eq!( - one_time_keys.curve25519(), - one_time_keys.get("curve25519").unwrap() - ); - - account.mark_keys_as_published().await; - let one_time_keys = account.one_time_keys().await; - assert!(one_time_keys.curve25519().is_empty()); - } - - #[tokio::test] - async fn session_creation() { - let alice = Account::new(&alice_id(), &alice_device_id()); - let bob = Account::new(&bob_id(), &bob_device_id()); - let alice_keys = alice.identity_keys(); - alice.generate_one_time_keys_helper(1).await; - let one_time_keys = alice.one_time_keys().await; - alice.mark_keys_as_published().await; - - let one_time_key = one_time_keys - .curve25519() - .iter() - .next() - .unwrap() - .1 - .to_owned(); - - let one_time_key = SignedKey { - key: one_time_key, - signatures: BTreeMap::new(), - }; - - let mut bob_session = bob - .create_outbound_session_helper(alice_keys.curve25519(), &one_time_key) - .await - .unwrap(); - - let plaintext = "Hello world"; - - let message = bob_session.encrypt(plaintext).await; - - let prekey_message = match message.clone() { - OlmMessage::PreKey(m) => m, - OlmMessage::Message(_) => panic!("Incorrect message type"), - }; - - let bob_keys = bob.identity_keys(); - let mut alice_session = alice - .create_inbound_session(bob_keys.curve25519(), prekey_message.clone()) - .await - .unwrap(); - - assert!(alice_session - .matches(bob_keys.curve25519(), prekey_message) - .await - .unwrap()); - - assert_eq!(bob_session.session_id(), alice_session.session_id()); - - let decyrpted = alice_session.decrypt(message).await.unwrap(); - assert_eq!(plaintext, decyrpted); - } - - #[tokio::test] - async fn group_session_creation() { - let alice = Account::new(&alice_id(), &alice_device_id()); - let room_id = RoomId::try_from("!test:localhost").unwrap(); - - let (outbound, _) = alice.create_group_session_pair(&room_id).await; - - assert_eq!(0, outbound.message_index().await); - assert!(!outbound.shared()); - outbound.mark_as_shared(); - assert!(outbound.shared()); - - let inbound = InboundGroupSession::new( - "test_key", - "test_key", - &room_id, - outbound.session_key().await, - ) - .unwrap(); - - assert_eq!(0, inbound.first_known_index().await); - - assert_eq!(outbound.session_id(), inbound.session_id()); - - let plaintext = "This is a secret to everybody".to_owned(); - let ciphertext = outbound.encrypt_helper(plaintext.clone()).await; - - assert_eq!( - plaintext, - inbound.decrypt_helper(ciphertext).await.unwrap().0 - ); - } -} diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs new file mode 100644 index 00000000..9178e4de --- /dev/null +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -0,0 +1,544 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use matrix_sdk_common::instant::Instant; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::fmt; +use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; +use std::sync::Arc; + +use matrix_sdk_common::locks::Mutex; +use serde_json::{json, Value}; +use std::collections::BTreeMap; + +pub use olm_rs::account::IdentityKeys; +use olm_rs::account::{OlmAccount, OneTimeKeys}; +use olm_rs::errors::{OlmAccountError, OlmSessionError}; +use olm_rs::PicklingMode; + +use crate::device::Device; +use crate::error::SessionCreationError; +pub use olm_rs::{ + session::{OlmMessage, PreKeyMessage}, + utility::OlmUtility, +}; + +use matrix_sdk_common::{ + api::r0::keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey, SignedKey}, + events::Algorithm, + identifiers::{DeviceId, RoomId, UserId}, +}; + +use super::{InboundGroupSession, OutboundGroupSession, Session}; + +/// Account holding identity keys for which sessions can be created. +/// +/// An account is the central identity for encrypted communication between two +/// devices. +#[derive(Clone)] +pub struct Account { + user_id: Arc, + device_id: Arc>, + inner: Arc>, + identity_keys: Arc, + shared: Arc, + /// The number of signed one-time keys we have uploaded to the server. If + /// this is None, no action will be taken. After a sync request the client + /// needs to set this for us, depending on the count we will suggest the + /// client to upload new keys. + uploaded_signed_key_count: Arc, +} + +// #[cfg_attr(tarpaulin, skip)] +impl fmt::Debug for Account { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Account") + .field("identity_keys", self.identity_keys()) + .field("shared", &self.shared()) + .finish() + } +} + +impl Account { + const ALGORITHMS: &'static [&'static Algorithm] = &[ + &Algorithm::OlmV1Curve25519AesSha2, + &Algorithm::MegolmV1AesSha2, + ]; + + /// Create a fresh new account, this will generate the identity key-pair. + #[allow(clippy::ptr_arg)] + pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self { + let account = OlmAccount::new(); + let identity_keys = account.parsed_identity_keys(); + + Account { + user_id: Arc::new(user_id.to_owned()), + device_id: Arc::new(device_id.into()), + inner: Arc::new(Mutex::new(account)), + identity_keys: Arc::new(identity_keys), + shared: Arc::new(AtomicBool::new(false)), + uploaded_signed_key_count: Arc::new(AtomicI64::new(0)), + } + } + + /// Get the public parts of the identity keys for the account. + pub fn identity_keys(&self) -> &IdentityKeys { + &self.identity_keys + } + + /// Update the uploaded key count. + /// + /// # Arguments + /// + /// * `new_count` - The new count that was reported by the server. + pub(crate) fn update_uploaded_key_count(&self, new_count: u64) { + let key_count = i64::try_from(new_count).unwrap_or(i64::MAX); + self.uploaded_signed_key_count + .store(key_count, Ordering::Relaxed); + } + + /// Get the currently known uploaded key count. + pub fn uploaded_key_count(&self) -> i64 { + self.uploaded_signed_key_count.load(Ordering::Relaxed) + } + + /// Has the account been shared with the server. + pub fn shared(&self) -> bool { + self.shared.load(Ordering::Relaxed) + } + + /// Mark the account as shared. + /// + /// Messages shouldn't be encrypted with the session before it has been + /// shared. + pub(crate) fn mark_as_shared(&self) { + self.shared.store(true, Ordering::Relaxed); + } + + /// Get the one-time keys of the account. + /// + /// This can be empty, keys need to be generated first. + pub(crate) async fn one_time_keys(&self) -> OneTimeKeys { + self.inner.lock().await.parsed_one_time_keys() + } + + /// Generate count number of one-time keys. + pub(crate) async fn generate_one_time_keys_helper(&self, count: usize) { + self.inner.lock().await.generate_one_time_keys(count); + } + + /// Get the maximum number of one-time keys the account can hold. + pub(crate) async fn max_one_time_keys(&self) -> usize { + self.inner.lock().await.max_number_of_one_time_keys() + } + + /// Get a tuple of device and one-time keys that need to be uploaded. + /// + /// Returns an empty error if no keys need to be uploaded. + pub(crate) async fn generate_one_time_keys(&self) -> Result { + let count = self.uploaded_key_count() as u64; + let max_keys = self.max_one_time_keys().await; + let max_on_server = (max_keys as u64) / 2; + + if count >= (max_on_server) { + return Err(()); + } + + let key_count = (max_on_server) - count; + let key_count: usize = key_count.try_into().unwrap_or(max_keys); + + self.generate_one_time_keys_helper(key_count).await; + Ok(key_count as u64) + } + + /// Should account or one-time keys be uploaded to the server. + pub(crate) async fn should_upload_keys(&self) -> bool { + if !self.shared() { + return true; + } + + let count = self.uploaded_key_count() as u64; + + // If we have a known key count, check that we have more than + // max_one_time_Keys() / 2, otherwise tell the client to upload more. + let max_keys = self.max_one_time_keys().await as u64; + // If there are more keys already uploaded than max_key / 2 + // bail out returning false, this also avoids overflow. + if count > (max_keys / 2) { + return false; + } + + let key_count = (max_keys / 2) - count; + key_count > 0 + } + + /// Get a tuple of device and one-time keys that need to be uploaded. + /// + /// Returns an empty error if no keys need to be uploaded. + pub(crate) async fn keys_for_upload( + &self, + ) -> Result< + ( + Option, + Option>, + ), + (), + > { + if !self.should_upload_keys().await { + return Err(()); + } + + let device_keys = if !self.shared() { + Some(self.device_keys().await) + } else { + None + }; + + let one_time_keys = self.signed_one_time_keys().await.ok(); + + Ok((device_keys, one_time_keys)) + } + + /// Mark the current set of one-time keys as being published. + pub(crate) async fn mark_keys_as_published(&self) { + self.inner.lock().await.mark_keys_as_published(); + } + + /// Sign the given string using the accounts signing key. + /// + /// Returns the signature as a base64 encoded string. + pub async fn sign(&self, string: &str) -> String { + self.inner.lock().await.sign(string) + } + + /// Store the account as a base64 encoded string. + /// + /// # Arguments + /// + /// * `pickle_mode` - The mode that was used to pickle the account, either an + /// unencrypted mode or an encrypted using passphrase. + pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { + self.inner.lock().await.pickle(pickle_mode) + } + + /// Restore an account from a previously pickled string. + /// + /// # Arguments + /// + /// * `pickle` - The pickled string of the account. + /// + /// * `pickle_mode` - The mode that was used to pickle the account, either an + /// unencrypted mode or an encrypted using passphrase. + /// + /// * `shared` - Boolean determining if the account was uploaded to the + /// server. + #[allow(clippy::ptr_arg)] + pub fn from_pickle( + pickle: String, + pickle_mode: PicklingMode, + shared: bool, + uploaded_signed_key_count: i64, + user_id: &UserId, + device_id: &DeviceId, + ) -> Result { + let account = OlmAccount::unpickle(pickle, pickle_mode)?; + let identity_keys = account.parsed_identity_keys(); + + Ok(Account { + user_id: Arc::new(user_id.to_owned()), + device_id: Arc::new(device_id.into()), + inner: Arc::new(Mutex::new(account)), + identity_keys: Arc::new(identity_keys), + shared: Arc::new(AtomicBool::from(shared)), + uploaded_signed_key_count: Arc::new(AtomicI64::new(uploaded_signed_key_count)), + }) + } + + /// Sign the device keys of the account and return them so they can be + /// uploaded. + pub(crate) async fn device_keys(&self) -> DeviceKeys { + let identity_keys = self.identity_keys(); + + let mut keys = BTreeMap::new(); + + keys.insert( + AlgorithmAndDeviceId(KeyAlgorithm::Curve25519, (*self.device_id).clone()), + identity_keys.curve25519().to_owned(), + ); + keys.insert( + AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), + identity_keys.ed25519().to_owned(), + ); + + let device_keys = json!({ + "user_id": (*self.user_id).clone(), + "device_id": (*self.device_id).clone(), + "algorithms": Account::ALGORITHMS, + "keys": keys, + }); + + let mut signatures = BTreeMap::new(); + + let mut signature = BTreeMap::new(); + signature.insert( + AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), + self.sign_json(&device_keys).await, + ); + signatures.insert((*self.user_id).clone(), signature); + + DeviceKeys { + user_id: (*self.user_id).clone(), + device_id: (*self.device_id).clone(), + algorithms: vec![ + Algorithm::OlmV1Curve25519AesSha2, + Algorithm::MegolmV1AesSha2, + ], + keys, + signatures, + unsigned: None, + } + } + + /// Convert a JSON value to the canonical representation and sign the JSON + /// string. + /// + /// # Arguments + /// + /// * `json` - The value that should be converted into a canonical JSON + /// string. + /// + /// # Panic + /// + /// Panics if the json value can't be serialized. + pub async fn sign_json(&self, json: &Value) -> String { + let canonical_json = cjson::to_string(json) + .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", json))); + self.sign(&canonical_json).await + } + + /// Generate, sign and prepare one-time keys to be uploaded. + /// + /// If no one-time keys need to be uploaded returns an empty error. + pub(crate) async fn signed_one_time_keys( + &self, + ) -> Result, ()> { + let _ = self.generate_one_time_keys().await?; + + let one_time_keys = self.one_time_keys().await; + let mut one_time_key_map = BTreeMap::new(); + + for (key_id, key) in one_time_keys.curve25519().iter() { + let key_json = json!({ + "key": key, + }); + + let signature = self.sign_json(&key_json).await; + + let mut signature_map = BTreeMap::new(); + + signature_map.insert( + AlgorithmAndDeviceId(KeyAlgorithm::Ed25519, (*self.device_id).clone()), + signature, + ); + + let mut signatures = BTreeMap::new(); + signatures.insert((*self.user_id).clone(), signature_map); + + let signed_key = SignedKey { + key: key.to_owned(), + signatures, + }; + + one_time_key_map.insert( + AlgorithmAndDeviceId(KeyAlgorithm::SignedCurve25519, key_id.as_str().into()), + OneTimeKey::SignedKey(signed_key), + ); + } + + Ok(one_time_key_map) + } + + /// Create a new session with another account given a one-time key. + /// + /// Returns the newly created session or a `OlmSessionError` if creating a + /// session failed. + /// + /// # Arguments + /// * `their_identity_key` - The other account's identity/curve25519 key. + /// + /// * `their_one_time_key` - A signed one-time key that the other account + /// created and shared with us. + pub(crate) async fn create_outbound_session_helper( + &self, + their_identity_key: &str, + their_one_time_key: &SignedKey, + ) -> Result { + let session = self + .inner + .lock() + .await + .create_outbound_session(their_identity_key, &their_one_time_key.key)?; + + let now = Instant::now(); + let session_id = session.session_id(); + + Ok(Session { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(session_id), + sender_key: Arc::new(their_identity_key.to_owned()), + creation_time: Arc::new(now), + last_use_time: Arc::new(now), + }) + } + + /// Create a new session with another account given a one-time key and a + /// device. + /// + /// Returns the newly created session or a `OlmSessionError` if creating a + /// session failed. + /// + /// # Arguments + /// * `device` - The other account's device. + /// + /// * `key_map` - A map from the algorithm and device id to the one-time + /// key that the other account created and shared with us. + pub(crate) async fn create_outbound_session( + &self, + device: Device, + key_map: &BTreeMap, + ) -> Result { + let one_time_key = key_map.values().next().ok_or_else(|| { + SessionCreationError::OneTimeKeyMissing( + device.user_id().to_owned(), + device.device_id().into(), + ) + })?; + + let one_time_key = match one_time_key { + OneTimeKey::SignedKey(k) => k, + OneTimeKey::Key(_) => { + return Err(SessionCreationError::OneTimeKeyNotSigned( + device.user_id().to_owned(), + device.device_id().into(), + )); + } + }; + + device.verify_one_time_key(&one_time_key).map_err(|e| { + SessionCreationError::InvalidSignature( + device.user_id().to_owned(), + device.device_id().into(), + e, + ) + })?; + + let curve_key = device.get_key(KeyAlgorithm::Curve25519).ok_or_else(|| { + SessionCreationError::DeviceMissingCurveKey( + device.user_id().to_owned(), + device.device_id().into(), + ) + })?; + + self.create_outbound_session_helper(curve_key, &one_time_key) + .await + .map_err(|e| { + SessionCreationError::OlmError( + device.user_id().to_owned(), + device.device_id().into(), + e, + ) + }) + } + + /// Create a new session with another account given a pre-key Olm message. + /// + /// Returns the newly created session or a `OlmSessionError` if creating a + /// session failed. + /// + /// # Arguments + /// * `their_identity_key` - The other account's identitiy/curve25519 key. + /// + /// * `message` - A pre-key Olm message that was sent to us by the other + /// account. + pub(crate) async fn create_inbound_session( + &self, + their_identity_key: &str, + message: PreKeyMessage, + ) -> Result { + let session = self + .inner + .lock() + .await + .create_inbound_session_from(their_identity_key, message)?; + + self.inner + .lock() + .await + .remove_one_time_keys(&session) + .expect( + "Session was successfully created but the account doesn't hold a matching one-time key", + ); + + let now = Instant::now(); + let session_id = session.session_id(); + + Ok(Session { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(session_id), + sender_key: Arc::new(their_identity_key.to_owned()), + creation_time: Arc::new(now), + last_use_time: Arc::new(now), + }) + } + + /// Create a group session pair. + /// + /// This session pair can be used to encrypt and decrypt messages meant for + /// a large group of participants. + /// + /// The outbound session is used to encrypt messages while the inbound one + /// is used to decrypt messages encrypted by the outbound one. + /// + /// # Arguments + /// + /// * `room_id` - The ID of the room where the group session will be used. + pub(crate) async fn create_group_session_pair( + &self, + room_id: &RoomId, + ) -> (OutboundGroupSession, InboundGroupSession) { + let outbound = + OutboundGroupSession::new(self.device_id.clone(), self.identity_keys.clone(), room_id); + let identity_keys = self.identity_keys(); + + let sender_key = identity_keys.curve25519(); + let signing_key = identity_keys.ed25519(); + + let inbound = InboundGroupSession::new( + sender_key, + signing_key, + &room_id, + outbound.session_key().await, + ) + .expect("Can't create inbound group session from a newly created outbound group session"); + + (outbound, inbound) + } +} + +impl PartialEq for Account { + fn eq(&self, other: &Self) -> bool { + self.identity_keys() == other.identity_keys() && self.shared() == other.shared() + } +} diff --git a/matrix_sdk_crypto/src/olm/group_sessions.rs b/matrix_sdk_crypto/src/olm/group_sessions.rs new file mode 100644 index 00000000..53fe36ca --- /dev/null +++ b/matrix_sdk_crypto/src/olm/group_sessions.rs @@ -0,0 +1,400 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use matrix_sdk_common::instant::Instant; +use std::convert::TryInto; +use std::fmt; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; + +use matrix_sdk_common::locks::Mutex; +use serde::Serialize; +use serde_json::{json, Value}; +use zeroize::Zeroize; + +pub use olm_rs::account::IdentityKeys; +use olm_rs::errors::OlmGroupSessionError; +use olm_rs::inbound_group_session::OlmInboundGroupSession; +use olm_rs::outbound_group_session::OlmOutboundGroupSession; +use olm_rs::PicklingMode; + +use crate::error::{EventError, MegolmResult}; +pub use olm_rs::{ + session::{OlmMessage, PreKeyMessage}, + utility::OlmUtility, +}; + +use matrix_sdk_common::{ + events::{ + room::{ + encrypted::{EncryptedEventContent, MegolmV1AesSha2Content}, + message::MessageEventContent, + }, + AnySyncRoomEvent, EventJson, EventType, SyncMessageEvent, + }, + identifiers::{DeviceId, RoomId}, +}; + +/// The private session key of a group session. +/// Can be used to create a new inbound group session. +#[derive(Clone, Debug, Serialize, Zeroize)] +#[zeroize(drop)] +pub struct GroupSessionKey(pub String); + +/// Inbound group session. +/// +/// Inbound group sessions are used to exchange room messages between a group of +/// participants. Inbound group sessions are used to decrypt the room messages. +#[derive(Clone)] +pub struct InboundGroupSession { + inner: Arc>, + session_id: Arc, + pub(crate) sender_key: Arc, + pub(crate) signing_key: Arc, + pub(crate) room_id: Arc, + forwarding_chains: Arc>>>, +} + +impl InboundGroupSession { + /// Create a new inbound group session for the given room. + /// + /// These sessions are used to decrypt room messages. + /// + /// # Arguments + /// + /// * `sender_key` - The public curve25519 key of the account that + /// sent us the session + /// + /// * `signing_key` - The public ed25519 key of the account that + /// sent us the session. + /// + /// * `room_id` - The id of the room that the session is used in. + /// + /// * `session_key` - The private session key that is used to decrypt + /// messages. + pub fn new( + sender_key: &str, + signing_key: &str, + room_id: &RoomId, + session_key: GroupSessionKey, + ) -> Result { + let session = OlmInboundGroupSession::new(&session_key.0)?; + let session_id = session.session_id(); + + Ok(InboundGroupSession { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(session_id), + sender_key: Arc::new(sender_key.to_owned()), + signing_key: Arc::new(signing_key.to_owned()), + room_id: Arc::new(room_id.clone()), + forwarding_chains: Arc::new(Mutex::new(None)), + }) + } + + /// Store the group session as a base64 encoded string. + /// + /// # Arguments + /// + /// * `pickle_mode` - The mode that was used to pickle the group session, + /// either an unencrypted mode or an encrypted using passphrase. + pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { + self.inner.lock().await.pickle(pickle_mode) + } + + /// Restore a Session from a previously pickled string. + /// + /// Returns the restored group session or a `OlmGroupSessionError` if there + /// was an error. + /// + /// # Arguments + /// + /// * `pickle` - The pickled string of the group session session. + /// + /// * `pickle_mode` - The mode that was used to pickle the session, either + /// an unencrypted mode or an encrypted using passphrase. + /// + /// * `sender_key` - The public curve25519 key of the account that + /// sent us the session + /// + /// * `signing_key` - The public ed25519 key of the account that + /// sent us the session. + /// + /// * `room_id` - The id of the room that the session is used in. + pub fn from_pickle( + pickle: String, + pickle_mode: PicklingMode, + sender_key: String, + signing_key: String, + room_id: RoomId, + ) -> Result { + let session = OlmInboundGroupSession::unpickle(pickle, pickle_mode)?; + let session_id = session.session_id(); + + Ok(InboundGroupSession { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(session_id), + sender_key: Arc::new(sender_key), + signing_key: Arc::new(signing_key), + room_id: Arc::new(room_id), + forwarding_chains: Arc::new(Mutex::new(None)), + }) + } + + /// Returns the unique identifier for this session. + pub fn session_id(&self) -> &str { + &self.session_id + } + + /// Get the first message index we know how to decrypt. + pub async fn first_known_index(&self) -> u32 { + self.inner.lock().await.first_known_index() + } + + /// Decrypt the given ciphertext. + /// + /// Returns the decrypted plaintext or an `OlmGroupSessionError` if + /// decryption failed. + /// + /// # Arguments + /// + /// * `message` - The message that should be decrypted. + pub async fn decrypt_helper( + &self, + message: String, + ) -> Result<(String, u32), OlmGroupSessionError> { + self.inner.lock().await.decrypt(message) + } + + /// Decrypt an event from a room timeline. + /// + /// # Arguments + /// + /// * `event` - The event that should be decrypted. + pub async fn decrypt( + &self, + event: &SyncMessageEvent, + ) -> MegolmResult<(EventJson, u32)> { + let content = match &event.content { + EncryptedEventContent::MegolmV1AesSha2(c) => c, + _ => return Err(EventError::UnsupportedAlgorithm.into()), + }; + + let (plaintext, message_index) = self.decrypt_helper(content.ciphertext.clone()).await?; + + let mut decrypted_value = serde_json::from_str::(&plaintext)?; + let decrypted_object = decrypted_value + .as_object_mut() + .ok_or(EventError::NotAnObject)?; + + // TODO better number conversion here. + let server_ts = event + .origin_server_ts + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); + let server_ts: i64 = server_ts.try_into().unwrap_or_default(); + + decrypted_object.insert("sender".to_owned(), event.sender.to_string().into()); + decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into()); + decrypted_object.insert("origin_server_ts".to_owned(), server_ts.into()); + + decrypted_object.insert( + "unsigned".to_owned(), + serde_json::to_value(&event.unsigned).unwrap_or_default(), + ); + + Ok(( + serde_json::from_value::>(decrypted_value)?, + message_index, + )) + } +} + +// #[cfg_attr(tarpaulin, skip)] +impl fmt::Debug for InboundGroupSession { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("InboundGroupSession") + .field("session_id", &self.session_id()) + .finish() + } +} + +impl PartialEq for InboundGroupSession { + fn eq(&self, other: &Self) -> bool { + self.session_id() == other.session_id() + } +} + +/// Outbound group session. +/// +/// Outbound group sessions are used to exchange room messages between a group +/// of participants. Outbound group sessions are used to encrypt the room +/// messages. +#[derive(Clone)] +pub struct OutboundGroupSession { + inner: Arc>, + device_id: Arc>, + account_identity_keys: Arc, + session_id: Arc, + room_id: Arc, + creation_time: Arc, + message_count: Arc, + shared: Arc, +} + +impl OutboundGroupSession { + /// Create a new outbound group session for the given room. + /// + /// Outbound group sessions are used to encrypt room messages. + /// + /// # Arguments + /// + /// * `device_id` - The id of the device that created this session. + /// + /// * `identity_keys` - The identity keys of the account that created this + /// session. + /// + /// * `room_id` - The id of the room that the session is used in. + pub fn new( + device_id: Arc>, + identity_keys: Arc, + room_id: &RoomId, + ) -> Self { + let session = OlmOutboundGroupSession::new(); + let session_id = session.session_id(); + + OutboundGroupSession { + inner: Arc::new(Mutex::new(session)), + room_id: Arc::new(room_id.to_owned()), + device_id, + account_identity_keys: identity_keys, + session_id: Arc::new(session_id), + creation_time: Arc::new(Instant::now()), + message_count: Arc::new(AtomicUsize::new(0)), + shared: Arc::new(AtomicBool::new(false)), + } + } + + /// Encrypt the given plaintext using this session. + /// + /// Returns the encrypted ciphertext. + /// + /// # Arguments + /// + /// * `plaintext` - The plaintext that should be encrypted. + pub(crate) async fn encrypt_helper(&self, plaintext: String) -> String { + let session = self.inner.lock().await; + session.encrypt(plaintext) + } + + /// Encrypt a room message for the given room. + /// + /// Beware that a group session needs to be shared before this method can be + /// called using the `share_group_session()` method. + /// + /// Since group sessions can expire or become invalid if the room membership + /// changes client authors should check with the + /// `should_share_group_session()` method if a new group session needs to + /// be shared. + /// + /// # Arguments + /// + /// * `content` - The plaintext content of the message that should be + /// encrypted. + /// + /// # Panics + /// + /// Panics if the content can't be serialized. + pub async fn encrypt(&self, content: MessageEventContent) -> EncryptedEventContent { + let json_content = json!({ + "content": content, + "room_id": &*self.room_id, + "type": EventType::RoomMessage, + }); + + let plaintext = cjson::to_string(&json_content).unwrap_or_else(|_| { + panic!(format!( + "Can't serialize {} to canonical JSON", + json_content + )) + }); + + let ciphertext = self.encrypt_helper(plaintext).await; + + EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content::new( + matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit { + ciphertext, + sender_key: self.account_identity_keys.curve25519().to_owned(), + session_id: self.session_id().to_owned(), + device_id: (&*self.device_id).to_owned(), + }, + )) + } + + /// Check if the session has expired and if it should be rotated. + /// + /// A session will expire after some time or if enough messages have been + /// encrypted using it. + pub fn expired(&self) -> bool { + // TODO implement this. + false + } + + /// Mark the session as shared. + /// + /// Messages shouldn't be encrypted with the session before it has been + /// shared. + pub fn mark_as_shared(&self) { + self.shared.store(true, Ordering::Relaxed); + } + + /// Check if the session has been marked as shared. + pub fn shared(&self) -> bool { + self.shared.load(Ordering::Relaxed) + } + + /// Get the session key of this session. + /// + /// A session key can be used to to create an `InboundGroupSession`. + pub async fn session_key(&self) -> GroupSessionKey { + let session = self.inner.lock().await; + GroupSessionKey(session.session_key()) + } + + /// Returns the unique identifier for this session. + pub fn session_id(&self) -> &str { + &self.session_id + } + + /// Get the current message index for this session. + /// + /// Each message is sent with an increasing index. This returns the + /// message index that will be used for the next encrypted message. + pub async fn message_index(&self) -> u32 { + let session = self.inner.lock().await; + session.session_message_index() + } +} + +// #[cfg_attr(tarpaulin, skip)] +impl std::fmt::Debug for OutboundGroupSession { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OutboundGroupSession") + .field("session_id", &self.session_id) + .field("room_id", &self.room_id) + .field("creation_time", &self.creation_time) + .field("message_count", &self.message_count) + .finish() + } +} diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs new file mode 100644 index 00000000..39157d87 --- /dev/null +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -0,0 +1,208 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod account; +mod group_sessions; +mod session; + +pub use account::{Account, IdentityKeys}; +pub use group_sessions::{GroupSessionKey, InboundGroupSession, OutboundGroupSession}; +pub use session::{OlmMessage, Session}; + +#[cfg(test)] +pub(crate) mod test { + use crate::olm::{Account, InboundGroupSession, Session}; + use matrix_sdk_common::api::r0::keys::SignedKey; + use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; + use olm_rs::session::OlmMessage; + use std::collections::BTreeMap; + use std::convert::TryFrom; + + fn alice_id() -> UserId { + UserId::try_from("@alice:example.org").unwrap() + } + + fn alice_device_id() -> Box { + "ALICEDEVICE".into() + } + + fn bob_id() -> UserId { + UserId::try_from("@bob:example.org").unwrap() + } + + fn bob_device_id() -> Box { + "BOBDEVICE".into() + } + + pub(crate) async fn get_account_and_session() -> (Account, Session) { + let alice = Account::new(&alice_id(), &alice_device_id()); + let bob = Account::new(&bob_id(), &bob_device_id()); + + bob.generate_one_time_keys_helper(1).await; + let one_time_key = bob + .one_time_keys() + .await + .curve25519() + .iter() + .next() + .unwrap() + .1 + .to_owned(); + let one_time_key = SignedKey { + key: one_time_key, + signatures: BTreeMap::new(), + }; + let sender_key = bob.identity_keys().curve25519().to_owned(); + let session = alice + .create_outbound_session_helper(&sender_key, &one_time_key) + .await + .unwrap(); + + (alice, session) + } + + #[test] + fn account_creation() { + let account = Account::new(&alice_id(), &alice_device_id()); + let identyty_keys = account.identity_keys(); + + assert!(!account.shared()); + assert!(!identyty_keys.ed25519().is_empty()); + assert_ne!(identyty_keys.values().len(), 0); + assert_ne!(identyty_keys.keys().len(), 0); + assert_ne!(identyty_keys.iter().len(), 0); + assert!(identyty_keys.contains_key("ed25519")); + assert_eq!( + identyty_keys.ed25519(), + identyty_keys.get("ed25519").unwrap() + ); + assert!(!identyty_keys.curve25519().is_empty()); + + account.mark_as_shared(); + assert!(account.shared()); + } + + #[tokio::test] + async fn one_time_keys_creation() { + let account = Account::new(&alice_id(), &alice_device_id()); + let one_time_keys = account.one_time_keys().await; + + assert!(one_time_keys.curve25519().is_empty()); + assert_ne!(account.max_one_time_keys().await, 0); + + account.generate_one_time_keys_helper(10).await; + let one_time_keys = account.one_time_keys().await; + + assert!(!one_time_keys.curve25519().is_empty()); + assert_ne!(one_time_keys.values().len(), 0); + assert_ne!(one_time_keys.keys().len(), 0); + assert_ne!(one_time_keys.iter().len(), 0); + assert!(one_time_keys.contains_key("curve25519")); + assert_eq!(one_time_keys.curve25519().keys().len(), 10); + assert_eq!( + one_time_keys.curve25519(), + one_time_keys.get("curve25519").unwrap() + ); + + account.mark_keys_as_published().await; + let one_time_keys = account.one_time_keys().await; + assert!(one_time_keys.curve25519().is_empty()); + } + + #[tokio::test] + async fn session_creation() { + let alice = Account::new(&alice_id(), &alice_device_id()); + let bob = Account::new(&bob_id(), &bob_device_id()); + let alice_keys = alice.identity_keys(); + alice.generate_one_time_keys_helper(1).await; + let one_time_keys = alice.one_time_keys().await; + alice.mark_keys_as_published().await; + + let one_time_key = one_time_keys + .curve25519() + .iter() + .next() + .unwrap() + .1 + .to_owned(); + + let one_time_key = SignedKey { + key: one_time_key, + signatures: BTreeMap::new(), + }; + + let mut bob_session = bob + .create_outbound_session_helper(alice_keys.curve25519(), &one_time_key) + .await + .unwrap(); + + let plaintext = "Hello world"; + + let message = bob_session.encrypt(plaintext).await; + + let prekey_message = match message.clone() { + OlmMessage::PreKey(m) => m, + OlmMessage::Message(_) => panic!("Incorrect message type"), + }; + + let bob_keys = bob.identity_keys(); + let mut alice_session = alice + .create_inbound_session(bob_keys.curve25519(), prekey_message.clone()) + .await + .unwrap(); + + assert!(alice_session + .matches(bob_keys.curve25519(), prekey_message) + .await + .unwrap()); + + assert_eq!(bob_session.session_id(), alice_session.session_id()); + + let decyrpted = alice_session.decrypt(message).await.unwrap(); + assert_eq!(plaintext, decyrpted); + } + + #[tokio::test] + async fn group_session_creation() { + let alice = Account::new(&alice_id(), &alice_device_id()); + let room_id = RoomId::try_from("!test:localhost").unwrap(); + + let (outbound, _) = alice.create_group_session_pair(&room_id).await; + + assert_eq!(0, outbound.message_index().await); + assert!(!outbound.shared()); + outbound.mark_as_shared(); + assert!(outbound.shared()); + + let inbound = InboundGroupSession::new( + "test_key", + "test_key", + &room_id, + outbound.session_key().await, + ) + .unwrap(); + + assert_eq!(0, inbound.first_known_index().await); + + assert_eq!(outbound.session_id(), inbound.session_id()); + + let plaintext = "This is a secret to everybody".to_owned(); + let ciphertext = outbound.encrypt_helper(plaintext.clone()).await; + + assert_eq!( + plaintext, + inbound.decrypt_helper(ciphertext).await.unwrap().0 + ); + } +} diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs new file mode 100644 index 00000000..7438cbf5 --- /dev/null +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -0,0 +1,161 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use matrix_sdk_common::instant::Instant; +use std::fmt; +use std::sync::Arc; + +use matrix_sdk_common::locks::Mutex; + +pub use olm_rs::account::IdentityKeys; +use olm_rs::errors::OlmSessionError; +use olm_rs::session::OlmSession; +use olm_rs::PicklingMode; + +pub use olm_rs::{ + session::{OlmMessage, PreKeyMessage}, + utility::OlmUtility, +}; + +/// Cryptographic session that enables secure communication between two +/// `Account`s +#[derive(Clone)] +pub struct Session { + pub(crate) inner: Arc>, + pub(crate) session_id: Arc, + pub(crate) sender_key: Arc, + pub(crate) creation_time: Arc, + pub(crate) last_use_time: Arc, +} + +// #[cfg_attr(tarpaulin, skip)] +impl fmt::Debug for Session { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Session") + .field("session_id", &self.session_id()) + .field("sender_key", &self.sender_key) + .finish() + } +} + +impl Session { + /// Decrypt the given Olm message. + /// + /// Returns the decrypted plaintext or an `OlmSessionError` if decryption + /// failed. + /// + /// # Arguments + /// + /// * `message` - The Olm message that should be decrypted. + pub async fn decrypt(&mut self, message: OlmMessage) -> Result { + let plaintext = self.inner.lock().await.decrypt(message)?; + self.last_use_time = Arc::new(Instant::now()); + Ok(plaintext) + } + + /// Encrypt the given plaintext as a OlmMessage. + /// + /// Returns the encrypted Olm message. + /// + /// # Arguments + /// + /// * `plaintext` - The plaintext that should be encrypted. + pub async fn encrypt(&mut self, plaintext: &str) -> OlmMessage { + let message = self.inner.lock().await.encrypt(plaintext); + self.last_use_time = Arc::new(Instant::now()); + message + } + + /// Check if a pre-key Olm message was encrypted for this session. + /// + /// Returns true if it matches, false if not and a OlmSessionError if there + /// was an error checking if it matches. + /// + /// # Arguments + /// + /// * `their_identity_key` - The identity/curve25519 key of the account + /// that encrypted this Olm message. + /// + /// * `message` - The pre-key Olm message that should be checked. + pub async fn matches( + &self, + their_identity_key: &str, + message: PreKeyMessage, + ) -> Result { + self.inner + .lock() + .await + .matches_inbound_session_from(their_identity_key, message) + } + + /// Returns the unique identifier for this session. + pub fn session_id(&self) -> &str { + &self.session_id + } + + /// Store the session as a base64 encoded string. + /// + /// # Arguments + /// + /// * `pickle_mode` - The mode that was used to pickle the session, either + /// an unencrypted mode or an encrypted using passphrase. + pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { + self.inner.lock().await.pickle(pickle_mode) + } + + /// Restore a Session from a previously pickled string. + /// + /// Returns the restored Olm Session or a `OlmSessionError` if there was an + /// error. + /// + /// # Arguments + /// + /// * `pickle` - The pickled string of the session. + /// + /// * `pickle_mode` - The mode that was used to pickle the session, either + /// an unencrypted mode or an encrypted using passphrase. + /// + /// * `sender_key` - The public curve25519 key of the account that + /// established the session with us. + /// + /// * `creation_time` - The timestamp that marks when the session was + /// created. + /// + /// * `last_use_time` - The timestamp that marks when the session was + /// last used to encrypt or decrypt an Olm message. + pub fn from_pickle( + pickle: String, + pickle_mode: PicklingMode, + sender_key: String, + creation_time: Instant, + last_use_time: Instant, + ) -> Result { + let session = OlmSession::unpickle(pickle, pickle_mode)?; + let session_id = session.session_id(); + + Ok(Session { + inner: Arc::new(Mutex::new(session)), + session_id: Arc::new(session_id), + sender_key: Arc::new(sender_key), + creation_time: Arc::new(creation_time), + last_use_time: Arc::new(last_use_time), + }) + } +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.session_id() == other.session_id() + } +} From fe33430e9be406b0f777c2defffa754c93487613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 10:48:15 +0200 Subject: [PATCH 72/83] crypto: Use DeviceId instead of str everywhere. --- matrix_sdk_crypto/src/machine.rs | 2 +- matrix_sdk_crypto/src/memory_stores.rs | 6 +++--- matrix_sdk_crypto/src/store/sqlite.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 6bbbce33..5d05dd4a 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -164,7 +164,7 @@ impl OlmMachine { /// * `device_id` - The unique id of the device that owns this machine. pub async fn new_with_default_store>( user_id: &UserId, - device_id: &str, + device_id: &DeviceId, path: P, passphrase: &str, ) -> StoreResult { diff --git a/matrix_sdk_crypto/src/memory_stores.rs b/matrix_sdk_crypto/src/memory_stores.rs index 7cf6351c..72d91395 100644 --- a/matrix_sdk_crypto/src/memory_stores.rs +++ b/matrix_sdk_crypto/src/memory_stores.rs @@ -140,7 +140,7 @@ pub struct UserDevices { impl UserDevices { /// Get the specific device with the given device id. - pub fn get(&self, device_id: &str) -> Option { + pub fn get(&self, device_id: &DeviceId) -> Option { self.entries.get(device_id).cloned() } @@ -180,7 +180,7 @@ impl DeviceStore { } /// Get the device with the given device_id and belonging to the given user. - pub fn get(&self, user_id: &UserId, device_id: &str) -> Option { + pub fn get(&self, user_id: &UserId, device_id: &DeviceId) -> Option { self.entries .get(user_id) .and_then(|m| m.get(device_id).map(|d| d.value().clone())) @@ -189,7 +189,7 @@ impl DeviceStore { /// Remove the device with the given device_id and belonging to the given user. /// /// Returns the device if it was removed, None if it wasn't in the store. - pub fn remove(&self, user_id: &UserId, device_id: &str) -> Option { + pub fn remove(&self, user_id: &UserId, device_id: &DeviceId) -> Option { self.entries .get(user_id) .and_then(|m| m.remove(device_id)) diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 44feb951..5d69e466 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -66,7 +66,7 @@ impl SqliteStore { /// * `path` - The path where the database file should reside in. pub async fn open>( user_id: &UserId, - device_id: &str, + device_id: &DeviceId, path: P, ) -> Result { SqliteStore::open_helper(user_id, device_id, path, None).await @@ -88,7 +88,7 @@ impl SqliteStore { /// the encryption keys. pub async fn open_with_passphrase>( user_id: &UserId, - device_id: &str, + device_id: &DeviceId, path: P, passphrase: &str, ) -> Result { @@ -109,7 +109,7 @@ impl SqliteStore { async fn open_helper>( user_id: &UserId, - device_id: &str, + device_id: &DeviceId, path: P, passphrase: Option>, ) -> Result { @@ -804,7 +804,7 @@ mod test { use super::{Account, CryptoStore, InboundGroupSession, RoomId, Session, SqliteStore, TryFrom}; static USER_ID: &str = "@example:localhost"; - static DEVICE_ID: &str = "DEVICEID"; + static DEVICE_ID: &DeviceId = "DEVICEID"; async fn get_store(passphrase: Option<&str>) -> (SqliteStore, tempfile::TempDir) { let tmpdir = tempdir().unwrap(); From 3d6872607e02e4b90829d2f7e7054ee298788dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 11:12:20 +0200 Subject: [PATCH 73/83] crypto: Move the m.room_key content creation into the outbound group session. --- matrix_sdk_crypto/src/machine.rs | 19 ++++--------------- matrix_sdk_crypto/src/olm/group_sessions.rs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 5d05dd4a..cb364fd9 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -899,30 +899,19 @@ impl OlmMachine { I: IntoIterator, { self.create_outbound_group_session(room_id).await?; - let megolm_session = self.outbound_group_sessions.get(room_id).unwrap(); + let session = self.outbound_group_sessions.get(room_id).unwrap(); - if megolm_session.shared() { + if session.shared() { panic!("Session is already shared"); } - let session_id = megolm_session.session_id().to_owned(); - // TODO don't mark the session as shared automatically only, when all // the requests are done, failure to send these requests will likely end // up in wedged sessions. We'll need to store the requests and let the // caller mark them as sent using an UUID. - megolm_session.mark_as_shared(); + session.mark_as_shared(); - // TODO the key content creation can go into the OutboundGroupSession - // struct. - - let key_content = json!({ - "algorithm": Algorithm::MegolmV1AesSha2, - "room_id": room_id, - "session_id": session_id.clone(), - "session_key": megolm_session.session_key().await, - "chain_index": megolm_session.message_index().await, - }); + let key_content = session.as_json().await; let mut user_map = Vec::new(); diff --git a/matrix_sdk_crypto/src/olm/group_sessions.rs b/matrix_sdk_crypto/src/olm/group_sessions.rs index 53fe36ca..7b977d3c 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions.rs @@ -41,7 +41,7 @@ use matrix_sdk_common::{ encrypted::{EncryptedEventContent, MegolmV1AesSha2Content}, message::MessageEventContent, }, - AnySyncRoomEvent, EventJson, EventType, SyncMessageEvent, + Algorithm, AnySyncRoomEvent, EventJson, EventType, SyncMessageEvent, }, identifiers::{DeviceId, RoomId}, }; @@ -385,6 +385,18 @@ impl OutboundGroupSession { let session = self.inner.lock().await; session.session_message_index() } + + /// Get the outbound group session key as a json value that can be sent as a + /// m.room_key. + pub async fn as_json(&self) -> Value { + json!({ + "algorithm": Algorithm::MegolmV1AesSha2, + "room_id": &*self.room_id, + "session_id": &*self.session_id, + "session_key": self.session_key().await, + "chain_index": self.message_index().await, + }) + } } // #[cfg_attr(tarpaulin, skip)] From 3f1439fe28e2653414607d95fcd947fc6791f69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 12:03:05 +0200 Subject: [PATCH 74/83] crypto: Move the olm encryption logic into the Session struct. --- matrix_sdk_crypto/src/machine.rs | 61 ++++------------------- matrix_sdk_crypto/src/olm/account.rs | 4 +- matrix_sdk_crypto/src/olm/mod.rs | 2 +- matrix_sdk_crypto/src/olm/session.rs | 72 ++++++++++++++++++++++++++-- 4 files changed, 79 insertions(+), 60 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index cb364fd9..27a8f757 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -32,13 +32,10 @@ use super::{device::Device, store::Result as StoreResult, CryptoStore}; use matrix_sdk_common::api; use matrix_sdk_common::events::{ - forwarded_room_key::ForwardedRoomKeyEventContent, - room::encrypted::{CiphertextInfo, EncryptedEventContent, OlmV1Curve25519AesSha2Content}, - room::message::MessageEventContent, - room_key::RoomKeyEventContent, - room_key_request::RoomKeyRequestEventContent, - Algorithm, AnySyncRoomEvent, AnyToDeviceEvent, EventJson, EventType, SyncMessageEvent, - ToDeviceEvent, + forwarded_room_key::ForwardedRoomKeyEventContent, room::encrypted::EncryptedEventContent, + room::message::MessageEventContent, room_key::RoomKeyEventContent, + room_key_request::RoomKeyRequestEventContent, Algorithm, AnySyncRoomEvent, AnyToDeviceEvent, + EventJson, EventType, SyncMessageEvent, ToDeviceEvent, }; use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; use matrix_sdk_common::uuid::Uuid; @@ -50,7 +47,7 @@ use api::r0::{ to_device::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices}, }; -use serde_json::{json, Value}; +use serde_json::Value; use tracing::{debug, error, info, instrument, trace, warn}; /// A map from the algorithm and device id to a one-time key. @@ -807,52 +804,12 @@ impl OlmMachine { event_type: EventType, content: Value, ) -> OlmResult { - let identity_keys = self.account.identity_keys(); - - // TODO most of this could go into the session, the session already - // stores the curve key of the device, if we also store the ed25519 key - // with the session we'll only need to pass in the account to the - // session and all of this can live in the session. - - let recipient_signing_key = recipient_device - .get_key(KeyAlgorithm::Ed25519) - .ok_or(EventError::MissingSigningKey)?; - let recipient_sender_key = recipient_device - .get_key(KeyAlgorithm::Curve25519) - .ok_or(EventError::MissingSigningKey)?; - - let payload = json!({ - "sender": self.user_id, - "sender_device": self.device_id, - "keys": { - "ed25519": identity_keys.ed25519(), - }, - "recipient": recipient_device.user_id(), - "recipient_keys": { - "ed25519": recipient_signing_key, - }, - "type": event_type, - "content": content, - }); - - let plaintext = cjson::to_string(&payload) - .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", payload))); - - let ciphertext = session.encrypt(&plaintext).await.to_tuple(); - - let message_type: usize = ciphertext.0.into(); - - let ciphertext = CiphertextInfo::new(ciphertext.1, (message_type as u32).into()); - - let mut content = BTreeMap::new(); - - content.insert(recipient_sender_key.to_owned(), ciphertext); - + let message = session + .encrypt(self.account.clone(), recipient_device, event_type, content) + .await; self.store.save_sessions(&[session]).await?; - Ok(EncryptedEventContent::OlmV1Curve25519AesSha2( - OlmV1Curve25519AesSha2Content::new(content, identity_keys.curve25519().to_owned()), - )) + message } /// Should the client share a group session for the given room. diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 9178e4de..4cff7467 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -49,8 +49,8 @@ use super::{InboundGroupSession, OutboundGroupSession, Session}; /// devices. #[derive(Clone)] pub struct Account { - user_id: Arc, - device_id: Arc>, + pub(crate) user_id: Arc, + pub(crate) device_id: Arc>, inner: Arc>, identity_keys: Arc, shared: Arc, diff --git a/matrix_sdk_crypto/src/olm/mod.rs b/matrix_sdk_crypto/src/olm/mod.rs index 39157d87..e6e31c3f 100644 --- a/matrix_sdk_crypto/src/olm/mod.rs +++ b/matrix_sdk_crypto/src/olm/mod.rs @@ -149,7 +149,7 @@ pub(crate) mod test { let plaintext = "Hello world"; - let message = bob_session.encrypt(plaintext).await; + let message = bob_session.encrypt_helper(plaintext).await; let prekey_message = match message.clone() { OlmMessage::PreKey(m) => m, diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index 7438cbf5..fff93eab 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -12,22 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -use matrix_sdk_common::instant::Instant; +use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; -use matrix_sdk_common::locks::Mutex; - -pub use olm_rs::account::IdentityKeys; use olm_rs::errors::OlmSessionError; use olm_rs::session::OlmSession; use olm_rs::PicklingMode; +use serde_json::{json, Value}; + pub use olm_rs::{ session::{OlmMessage, PreKeyMessage}, utility::OlmUtility, }; +use super::Account; +use crate::error::{EventError, OlmResult}; +use crate::Device; + +use matrix_sdk_common::{ + api::r0::keys::KeyAlgorithm, + events::{ + room::encrypted::{CiphertextInfo, EncryptedEventContent, OlmV1Curve25519AesSha2Content}, + EventType, + }, + instant::Instant, + locks::Mutex, +}; + /// Cryptographic session that enables secure communication between two /// `Account`s #[derive(Clone)] @@ -71,12 +84,61 @@ impl Session { /// # Arguments /// /// * `plaintext` - The plaintext that should be encrypted. - pub async fn encrypt(&mut self, plaintext: &str) -> OlmMessage { + pub(crate) async fn encrypt_helper(&mut self, plaintext: &str) -> OlmMessage { let message = self.inner.lock().await.encrypt(plaintext); self.last_use_time = Arc::new(Instant::now()); message } + /// Encrypt the given event content content as an m.room.encrypted event + /// content. + pub async fn encrypt( + &mut self, + account: Account, + recipient_device: &Device, + event_type: EventType, + content: Value, + ) -> OlmResult { + let recipient_signing_key = recipient_device + .get_key(KeyAlgorithm::Ed25519) + .ok_or(EventError::MissingSigningKey)?; + let recipient_sender_key = recipient_device + .get_key(KeyAlgorithm::Curve25519) + .ok_or(EventError::MissingSigningKey)?; + + let payload = json!({ + "sender": account.user_id.to_string(), + "sender_device": account.device_id.as_ref(), + "keys": { + "ed25519": account.identity_keys().ed25519(), + }, + "recipient": recipient_device.user_id(), + "recipient_keys": { + "ed25519": recipient_signing_key, + }, + "type": event_type, + "content": content, + }); + + let plaintext = cjson::to_string(&payload) + .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", payload))); + + let ciphertext = self.encrypt_helper(&plaintext).await.to_tuple(); + + let message_type = ciphertext.0; + let ciphertext = CiphertextInfo::new(ciphertext.1, (message_type as u32).into()); + + let mut content = BTreeMap::new(); + content.insert(recipient_sender_key.to_owned(), ciphertext); + + Ok(EncryptedEventContent::OlmV1Curve25519AesSha2( + OlmV1Curve25519AesSha2Content::new( + content, + account.identity_keys().curve25519().to_owned(), + ), + )) + } + /// Check if a pre-key Olm message was encrypted for this session. /// /// Returns true if it matches, false if not and a OlmSessionError if there From e50cf39a17752879902b5ade1acc551a3c20c556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 12:40:23 +0200 Subject: [PATCH 75/83] crypto: Store a copy of the user_id/device_id and identity keys in sessions. --- matrix_sdk_crypto/src/lib.rs | 2 +- matrix_sdk_crypto/src/olm/account.rs | 8 +++- matrix_sdk_crypto/src/olm/session.rs | 12 ++++- matrix_sdk_crypto/src/store/sqlite.rs | 67 ++++++++++++++++++--------- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index 148480ca..332e61db 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -37,7 +37,7 @@ pub use device::{Device, TrustState}; pub use error::{MegolmError, OlmError}; pub use machine::{OlmMachine, OneTimeKeys}; pub use memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; -pub use olm::{Account, InboundGroupSession, OutboundGroupSession, Session}; +pub use olm::{Account, IdentityKeys, InboundGroupSession, OutboundGroupSession, Session}; #[cfg(feature = "sqlite-cryptostore")] pub use store::sqlite::SqliteStore; pub use store::{CryptoStore, CryptoStoreError}; diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 4cff7467..a1308666 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -52,7 +52,7 @@ pub struct Account { pub(crate) user_id: Arc, pub(crate) device_id: Arc>, inner: Arc>, - identity_keys: Arc, + pub(crate) identity_keys: Arc, shared: Arc, /// The number of signed one-time keys we have uploaded to the server. If /// this is None, no action will be taken. After a sync request the client @@ -395,6 +395,9 @@ impl Account { let session_id = session.session_id(); Ok(Session { + user_id: self.user_id.clone(), + device_id: self.device_id.clone(), + our_identity_keys: self.identity_keys.clone(), inner: Arc::new(Mutex::new(session)), session_id: Arc::new(session_id), sender_key: Arc::new(their_identity_key.to_owned()), @@ -495,6 +498,9 @@ impl Account { let session_id = session.session_id(); Ok(Session { + user_id: self.user_id.clone(), + device_id: self.device_id.clone(), + our_identity_keys: self.identity_keys.clone(), inner: Arc::new(Mutex::new(session)), session_id: Arc::new(session_id), sender_key: Arc::new(their_identity_key.to_owned()), diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index fff93eab..5bf9e6c2 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -27,7 +27,7 @@ pub use olm_rs::{ utility::OlmUtility, }; -use super::Account; +use super::{Account, IdentityKeys}; use crate::error::{EventError, OlmResult}; use crate::Device; @@ -37,6 +37,7 @@ use matrix_sdk_common::{ room::encrypted::{CiphertextInfo, EncryptedEventContent, OlmV1Curve25519AesSha2Content}, EventType, }, + identifiers::{DeviceId, UserId}, instant::Instant, locks::Mutex, }; @@ -45,6 +46,9 @@ use matrix_sdk_common::{ /// `Account`s #[derive(Clone)] pub struct Session { + pub(crate) user_id: Arc, + pub(crate) device_id: Arc>, + pub(crate) our_identity_keys: Arc, pub(crate) inner: Arc>, pub(crate) session_id: Arc, pub(crate) sender_key: Arc, @@ -197,6 +201,9 @@ impl Session { /// * `last_use_time` - The timestamp that marks when the session was /// last used to encrypt or decrypt an Olm message. pub fn from_pickle( + user_id: Arc, + device_id: Arc>, + our_identity_keys: Arc, pickle: String, pickle_mode: PicklingMode, sender_key: String, @@ -207,6 +214,9 @@ impl Session { let session_id = session.session_id(); Ok(Session { + user_id, + device_id, + our_identity_keys, inner: Arc::new(Mutex::new(session)), session_id: Arc::new(session_id), sender_key: Arc::new(sender_key), diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 5d69e466..bc382d6b 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -26,9 +26,10 @@ use olm_rs::PicklingMode; use sqlx::{query, query_as, sqlite::SqliteQueryAs, Connect, Executor, SqliteConnection}; use zeroize::Zeroizing; -use super::{Account, CryptoStore, CryptoStoreError, InboundGroupSession, Result, Session}; +use super::{CryptoStore, CryptoStoreError, Result}; use crate::device::{Device, TrustState}; use crate::memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; +use crate::{Account, IdentityKeys, InboundGroupSession, Session}; use matrix_sdk_common::api::r0::keys::{AlgorithmAndDeviceId, KeyAlgorithm}; use matrix_sdk_common::events::Algorithm; use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; @@ -36,8 +37,8 @@ use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId}; /// SQLite based implementation of a `CryptoStore`. pub struct SqliteStore { user_id: Arc, - device_id: Arc, - account_id: Option, + device_id: Arc>, + account_info: Option, path: PathBuf, sessions: SessionStore, @@ -50,6 +51,11 @@ pub struct SqliteStore { pickle_passphrase: Option>, } +struct AccountInfo { + account_id: i64, + identity_keys: Arc, +} + static DATABASE_NAME: &str = "matrix-sdk-crypto.db"; impl SqliteStore { @@ -118,8 +124,8 @@ impl SqliteStore { let connection = SqliteConnection::connect(url.as_ref()).await?; let store = SqliteStore { user_id: Arc::new(user_id.to_owned()), - device_id: Arc::new(device_id.to_owned()), - account_id: None, + device_id: Arc::new(device_id.into()), + account_info: None, sessions: SessionStore::new(), inbound_group_sessions: GroupSessionStore::new(), devices: DeviceStore::new(), @@ -133,6 +139,10 @@ impl SqliteStore { Ok(store) } + fn account_id(&self) -> Option { + self.account_info.as_ref().map(|i| i.account_id) + } + async fn create_tables(&self) -> Result<()> { let mut connection = self.connection.lock().await; connection @@ -288,14 +298,17 @@ impl SqliteStore { } async fn load_sessions_for(&mut self, sender_key: &str) -> Result> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_info = self + .account_info + .as_ref() + .ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; let rows: Vec<(String, String, String, String)> = query_as( "SELECT pickle, sender_key, creation_time, last_use_time FROM sessions WHERE account_id = ? and sender_key = ?", ) - .bind(account_id) + .bind(account_info.account_id) .bind(sender_key) .fetch_all(&mut *connection) .await?; @@ -315,6 +328,9 @@ impl SqliteStore { .ok_or(CryptoStoreError::SessionTimestampError)?; Ok(Session::from_pickle( + self.user_id.clone(), + self.device_id.clone(), + account_info.identity_keys.clone(), pickle.to_string(), self.get_pickle_mode(), sender_key.to_string(), @@ -326,7 +342,7 @@ impl SqliteStore { } async fn load_inbound_group_sessions(&self) -> Result> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; let rows: Vec<(String, String, String, String)> = query_as( @@ -357,7 +373,7 @@ impl SqliteStore { } async fn save_tracked_user(&self, user: &UserId, dirty: bool) -> Result<()> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; query( @@ -378,7 +394,7 @@ impl SqliteStore { } async fn load_tracked_users(&self) -> Result<(HashSet, HashSet)> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; let rows: Vec<(String, bool)> = query_as( @@ -410,7 +426,7 @@ impl SqliteStore { } async fn load_devices(&self) -> Result { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; let rows: Vec<(i64, String, String, Option, i64)> = query_as( @@ -490,7 +506,7 @@ impl SqliteStore { } async fn save_device_helper(&self, device: Device) -> Result<()> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; @@ -573,20 +589,26 @@ impl CryptoStore for SqliteStore { WHERE user_id = ? and device_id = ?", ) .bind(self.user_id.as_str()) - .bind(&*self.device_id) + .bind((&*self.device_id).as_ref()) .fetch_optional(&mut *connection) .await?; let result = if let Some((id, pickle, shared, uploaded_key_count)) = row { - self.account_id = Some(id); - Some(Account::from_pickle( + let account = Account::from_pickle( pickle, self.get_pickle_mode(), shared, uploaded_key_count, &self.user_id, &self.device_id, - )?) + )?; + + self.account_info = Some(AccountInfo { + account_id: id, + identity_keys: account.identity_keys.clone(), + }); + + Some(account) } else { return Ok(None); }; @@ -640,19 +662,22 @@ impl CryptoStore for SqliteStore { .fetch_one(&mut *connection) .await?; - self.account_id = Some(account_id.0); + self.account_info = Some(AccountInfo { + account_id: account_id.0, + identity_keys: account.identity_keys.clone(), + }); Ok(()) } async fn save_sessions(&mut self, sessions: &[Session]) -> Result<()> { + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; + // TODO turn this into a transaction for session in sessions { self.lazy_load_sessions(&session.sender_key).await?; self.sessions.add(session.clone()).await; - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; - let session_id = session.session_id(); let creation_time = serde_json::to_string(&session.creation_time.elapsed())?; let last_use_time = serde_json::to_string(&session.last_use_time.elapsed())?; @@ -683,7 +708,7 @@ impl CryptoStore for SqliteStore { } async fn save_inbound_group_session(&mut self, session: InboundGroupSession) -> Result { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let pickle = session.pickle(self.get_pickle_mode()).await; let mut connection = self.connection.lock().await; let session_id = session.session_id(); @@ -753,7 +778,7 @@ impl CryptoStore for SqliteStore { } async fn delete_device(&self, device: Device) -> Result<()> { - let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; + let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; query( From c3f00c96f894307ecd90b5989c62d1d3a4cf3c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 12:46:06 +0200 Subject: [PATCH 76/83] crypto: Don't require the account to be passed when encrypting. --- matrix_sdk_crypto/src/machine.rs | 4 +--- matrix_sdk_crypto/src/olm/session.rs | 16 ++++++---------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 27a8f757..91399c9b 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -804,9 +804,7 @@ impl OlmMachine { event_type: EventType, content: Value, ) -> OlmResult { - let message = session - .encrypt(self.account.clone(), recipient_device, event_type, content) - .await; + let message = session.encrypt(recipient_device, event_type, content).await; self.store.save_sessions(&[session]).await?; message diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index 5bf9e6c2..2816e608 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -27,7 +27,7 @@ pub use olm_rs::{ utility::OlmUtility, }; -use super::{Account, IdentityKeys}; +use super::IdentityKeys; use crate::error::{EventError, OlmResult}; use crate::Device; @@ -98,7 +98,6 @@ impl Session { /// content. pub async fn encrypt( &mut self, - account: Account, recipient_device: &Device, event_type: EventType, content: Value, @@ -106,15 +105,12 @@ impl Session { let recipient_signing_key = recipient_device .get_key(KeyAlgorithm::Ed25519) .ok_or(EventError::MissingSigningKey)?; - let recipient_sender_key = recipient_device - .get_key(KeyAlgorithm::Curve25519) - .ok_or(EventError::MissingSigningKey)?; let payload = json!({ - "sender": account.user_id.to_string(), - "sender_device": account.device_id.as_ref(), + "sender": self.user_id.as_str(), + "sender_device": self.device_id.as_ref(), "keys": { - "ed25519": account.identity_keys().ed25519(), + "ed25519": self.our_identity_keys.ed25519(), }, "recipient": recipient_device.user_id(), "recipient_keys": { @@ -133,12 +129,12 @@ impl Session { let ciphertext = CiphertextInfo::new(ciphertext.1, (message_type as u32).into()); let mut content = BTreeMap::new(); - content.insert(recipient_sender_key.to_owned(), ciphertext); + content.insert((&*self.sender_key).to_owned(), ciphertext); Ok(EncryptedEventContent::OlmV1Curve25519AesSha2( OlmV1Curve25519AesSha2Content::new( content, - account.identity_keys().curve25519().to_owned(), + self.our_identity_keys.curve25519().to_string(), ), )) } From 451d9026049e92e48aba5ed6d334f104bb940987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 12:57:31 +0200 Subject: [PATCH 77/83] crypto: Allow that many arguments on the from_pickle session method. --- matrix_sdk_crypto/src/olm/session.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index 2816e608..46470e1e 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -196,6 +196,7 @@ impl Session { /// /// * `last_use_time` - The timestamp that marks when the session was /// last used to encrypt or decrypt an Olm message. + #[allow(clippy::too_many_arguments)] pub fn from_pickle( user_id: Arc, device_id: Arc>, From 861c07d5ce4bb7145cf532296a1ddef5ebbe8140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 12:59:15 +0200 Subject: [PATCH 78/83] cyrpto: Fix the docs for the Session encrypt method. --- matrix_sdk_crypto/src/olm/session.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index 46470e1e..ec64e833 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -183,6 +183,12 @@ impl Session { /// /// # Arguments /// + /// * `user_id` - Our own user id that the session belongs to. + /// + /// * `device_id` - Our own device id that the session belongs to. + /// + /// * `our_idenity_keys` - An clone of the Arc to our own identity keys. + /// /// * `pickle` - The pickled string of the session. /// /// * `pickle_mode` - The mode that was used to pickle the session, either From 24baf1fe0f52f2177355c53d48e57773cb095496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 13:04:51 +0200 Subject: [PATCH 79/83] crypto: More doc fixes. --- matrix_sdk_crypto/src/olm/session.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/matrix_sdk_crypto/src/olm/session.rs b/matrix_sdk_crypto/src/olm/session.rs index ec64e833..d6b11de8 100644 --- a/matrix_sdk_crypto/src/olm/session.rs +++ b/matrix_sdk_crypto/src/olm/session.rs @@ -96,6 +96,16 @@ impl Session { /// Encrypt the given event content content as an m.room.encrypted event /// content. + /// + /// # Arguments + /// + /// * `recipient_device` - The device for which this message is going to be + /// encrypted, this needs to be the device that was used to create this + /// session with. + /// + /// * `event_type` - The type of the event. + /// + /// * `content` - The content of the event. pub async fn encrypt( &mut self, recipient_device: &Device, From 578c927e587b8242710a21cb22942b19d23ee567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 14:13:10 +0200 Subject: [PATCH 80/83] crypto: Simplify the share_group_session method. --- matrix_sdk_crypto/src/error.rs | 11 +++ matrix_sdk_crypto/src/machine.rs | 130 ++++++++++++++----------------- 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/matrix_sdk_crypto/src/error.rs b/matrix_sdk_crypto/src/error.rs index 5ec901c3..0ac18fd2 100644 --- a/matrix_sdk_crypto/src/error.rs +++ b/matrix_sdk_crypto/src/error.rs @@ -50,6 +50,14 @@ pub enum OlmError { /// The session with a device has become corrupted. #[error("decryption failed likely because a Olm session was wedged")] SessionWedged, + + /// Encryption failed because the device does not have a valid Olm session + /// with us. + #[error( + "encryption failed because the device does not \ + have a valid Olm session with us" + )] + MissingSession, } /// Error representing a failure during a group encryption operation. @@ -94,6 +102,9 @@ pub enum EventError { #[error("the Encrypted message is missing the signing key of the sender")] MissingSigningKey, + #[error("the Encrypted message is missing the sender key")] + MissingSenderKey, + #[error("the Encrypted message is missing the field {0}")] MissingField(String), diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 91399c9b..255685b2 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -23,7 +23,6 @@ use std::result::Result as StdResult; use super::error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}; use super::olm::{ Account, GroupSessionKey, IdentityKeys, InboundGroupSession, OlmMessage, OutboundGroupSession, - Session, }; use super::store::memorystore::MemoryStore; #[cfg(feature = "sqlite-cryptostore")] @@ -796,14 +795,47 @@ impl OlmMachine { Ok(session.encrypt(content).await) } - /// Encrypt some JSON content using the given Olm session. + /// Encrypt the given event for the given Device + /// + /// # Arguments + /// + /// * `reciepient_device` - The device that the event should be encrypted + /// for. + /// + /// * `event_type` - The type of the event. + /// + /// * `content` - The content of the event that should be encrypted. async fn olm_encrypt( &mut self, - mut session: Session, recipient_device: &Device, event_type: EventType, content: Value, ) -> OlmResult { + let sender_key = if let Some(k) = recipient_device.get_key(KeyAlgorithm::Curve25519) { + k + } else { + warn!( + "Trying to encrypt a Megolm session for user {} on device {}, \ + but the device doesn't have a curve25519 key", + recipient_device.user_id(), + recipient_device.device_id() + ); + return Err(EventError::MissingSenderKey.into()); + }; + + let mut session = if let Some(s) = self.store.get_sessions(sender_key).await? { + let session = &s.lock().await[0]; + session.clone() + } else { + warn!( + "Trying to encrypt a Megolm session for user {} on device {}, \ + but no Olm session is found", + recipient_device.user_id(), + recipient_device.device_id() + ); + return Err(OlmError::MissingSession); + }; + let message = session.encrypt(recipient_device, event_type, content).await; self.store.save_sessions(&[session]).await?; @@ -866,79 +898,55 @@ impl OlmMachine { // caller mark them as sent using an UUID. session.mark_as_shared(); - let key_content = session.as_json().await; - - let mut user_map = Vec::new(); + let mut devices = Vec::new(); for user_id in users { for device in self.store.get_user_devices(user_id).await?.devices() { - let sender_key = if let Some(k) = device.get_key(KeyAlgorithm::Curve25519) { - k - } else { - warn!( - "The device {} of user {} doesn't have a curve 25519 key", - user_id, - device.device_id() - ); - // TODO mark the user for a key query. - continue; - }; - // TODO abort if the device isn't verified - let sessions = self.store.get_sessions(sender_key).await?; - - if let Some(s) = sessions { - let session = &s.lock().await[0]; - // TODO once the session has the all the device info, we - // won't need the device anymore to encrypt stuff with the - // session. - user_map.push((session.clone(), device.clone())); - } else { - warn!( - "Trying to encrypt a Megolm session for user - {} on device {}, but no Olm session is found", - user_id, - device.device_id() - ); - } + devices.push(device.clone()); } } - let mut message_vec = Vec::new(); + let mut requests = Vec::new(); + let key_content = session.as_json().await; - for user_map_chunk in user_map.chunks(OlmMachine::MAX_TO_DEVICE_MESSAGES) { + for device_map_chunk in devices.chunks(OlmMachine::MAX_TO_DEVICE_MESSAGES) { let mut messages = BTreeMap::new(); - for (session, device) in user_map_chunk { + for device in device_map_chunk { + let encrypted = self + .olm_encrypt(&device, EventType::RoomKey, key_content.clone()) + .await; + + let encrypted = match encrypted { + Ok(c) => c, + Err(OlmError::MissingSession) + | Err(OlmError::EventError(EventError::MissingSenderKey)) => { + continue; + } + Err(e) => return Err(e), + }; + if !messages.contains_key(device.user_id()) { messages.insert(device.user_id().clone(), BTreeMap::new()); }; let user_messages = messages.get_mut(device.user_id()).unwrap(); - let encrypted_content = self - .olm_encrypt( - session.clone(), - &device, - EventType::RoomKey, - key_content.clone(), - ) - .await?; - user_messages.insert( DeviceIdOrAllDevices::DeviceId(device.device_id().into()), - serde_json::value::to_raw_value(&encrypted_content)?, + serde_json::value::to_raw_value(&encrypted)?, ); } - message_vec.push(ToDeviceRequest { + requests.push(ToDeviceRequest { event_type: EventType::RoomEncrypted, txn_id: Uuid::new_v4().to_string(), messages, }); } - Ok(message_vec) + Ok(requests) } fn add_forwarded_room_key( @@ -1304,16 +1312,6 @@ mod test { async fn get_machine_pair_with_setup_sessions() -> (OlmMachine, OlmMachine) { let (mut alice, mut bob) = get_machine_pair_with_session().await; - let session = alice - .store - .get_sessions(bob.account.identity_keys().curve25519()) - .await - .unwrap() - .unwrap() - .lock() - .await[0] - .clone(); - let bob_device = alice .store .get_device(&bob.user_id, &bob.device_id) @@ -1324,7 +1322,7 @@ mod test { let event = ToDeviceEvent { sender: alice.user_id.clone(), content: alice - .olm_encrypt(session, &bob_device, EventType::Dummy, json!({})) + .olm_encrypt(&bob_device, EventType::Dummy, json!({})) .await .unwrap(), }; @@ -1593,16 +1591,6 @@ mod test { async fn test_olm_encryption() { let (mut alice, mut bob) = get_machine_pair_with_session().await; - let session = alice - .store - .get_sessions(bob.account.identity_keys().curve25519()) - .await - .unwrap() - .unwrap() - .lock() - .await[0] - .clone(); - let bob_device = alice .store .get_device(&bob.user_id, &bob.device_id) @@ -1613,7 +1601,7 @@ mod test { let event = ToDeviceEvent { sender: alice.user_id.clone(), content: alice - .olm_encrypt(session, &bob_device, EventType::Dummy, json!({})) + .olm_encrypt(&bob_device, EventType::Dummy, json!({})) .await .unwrap(), }; From a9d645cbcdff72643c392b004e110bc0f037c803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 16:28:41 +0200 Subject: [PATCH 81/83] crypto: Rewrite the device keys fetching in the SQLiteStore using filter_map. --- matrix_sdk_crypto/src/store/sqlite.rs | 28 ++++++++++++--------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index bc382d6b..76aec6ab 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -472,23 +472,19 @@ impl SqliteStore { .fetch_all(&mut *connection) .await?; - let mut keys = BTreeMap::new(); + let keys: BTreeMap = key_rows + .iter() + .filter_map(|row| { + let algorithm: &str = &row.0; + let algorithm = KeyAlgorithm::try_from(algorithm).ok()?; + let key = &row.1; - for row in key_rows { - let algorithm: &str = &row.0; - let algorithm = if let Ok(a) = KeyAlgorithm::try_from(algorithm) { - a - } else { - continue; - }; - - let key = &row.1; - - keys.insert( - AlgorithmAndDeviceId(algorithm, device_id.as_str().into()), - key.to_owned(), - ); - } + Some(( + AlgorithmAndDeviceId(algorithm, device_id.as_str().into()), + key.to_owned(), + )) + }) + .collect(); let device = Device::new( user_id, From 2481fbbd27d6c0b51828b7657762326612c01dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 21 Jul 2020 17:33:47 +0200 Subject: [PATCH 82/83] crypto: Store the device signatures with the devices as well. --- matrix_sdk_crypto/src/device.rs | 13 +++++ matrix_sdk_crypto/src/store/sqlite.rs | 83 +++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/matrix_sdk_crypto/src/device.rs b/matrix_sdk_crypto/src/device.rs index 17804bca..bcd84fbd 100644 --- a/matrix_sdk_crypto/src/device.rs +++ b/matrix_sdk_crypto/src/device.rs @@ -36,6 +36,7 @@ pub struct Device { device_id: Arc>, algorithms: Arc>, keys: Arc>, + signatures: Arc>>, display_name: Arc>, deleted: Arc, trust_state: Arc>, @@ -75,12 +76,14 @@ impl Device { trust_state: TrustState, algorithms: Vec, keys: BTreeMap, + signatures: BTreeMap>, ) -> Self { Device { user_id: Arc::new(user_id), device_id: Arc::new(device_id), display_name: Arc::new(display_name), trust_state: Arc::new(Atomic::new(trust_state)), + signatures: Arc::new(signatures), algorithms: Arc::new(algorithms), keys: Arc::new(keys), deleted: Arc::new(AtomicBool::new(false)), @@ -115,6 +118,11 @@ impl Device { &self.keys } + /// Get a map containing all the device signatures. + pub fn signatures(&self) -> &BTreeMap> { + &self.signatures + } + /// Get the trust state of the device. pub fn trust_state(&self) -> TrustState { self.trust_state.load(Ordering::Relaxed) @@ -144,6 +152,7 @@ impl Device { self.algorithms = Arc::new(device_keys.algorithms.clone()); self.keys = Arc::new(device_keys.keys.clone()); + self.signatures = Arc::new(device_keys.signatures.clone()); self.display_name = display_name; Ok(()) @@ -180,6 +189,8 @@ impl Device { #[cfg(test)] impl From<&OlmMachine> for Device { fn from(machine: &OlmMachine) -> Self { + let signatures = BTreeMap::new(); + Device { user_id: Arc::new(machine.user_id().clone()), device_id: Arc::new(machine.device_id().into()), @@ -204,6 +215,7 @@ impl From<&OlmMachine> for Device { ), display_name: Arc::new(None), deleted: Arc::new(AtomicBool::new(false)), + signatures: Arc::new(signatures), trust_state: Arc::new(Atomic::new(TrustState::Unset)), } } @@ -217,6 +229,7 @@ impl TryFrom<&DeviceKeys> for Device { user_id: Arc::new(device_keys.user_id.clone()), device_id: Arc::new(device_keys.device_id.clone()), algorithms: Arc::new(device_keys.algorithms.clone()), + signatures: Arc::new(device_keys.signatures.clone()), keys: Arc::new(device_keys.keys.clone()), display_name: Arc::new( device_keys diff --git a/matrix_sdk_crypto/src/store/sqlite.rs b/matrix_sdk_crypto/src/store/sqlite.rs index 76aec6ab..ba545a0c 100644 --- a/matrix_sdk_crypto/src/store/sqlite.rs +++ b/matrix_sdk_crypto/src/store/sqlite.rs @@ -272,6 +272,25 @@ impl SqliteStore { ) .await?; + connection + .execute( + r#" + CREATE TABLE IF NOT EXISTS device_signatures ( + "id" INTEGER NOT NULL PRIMARY KEY, + "device_id" INTEGER NOT NULL, + "user_id" TEXT NOT NULL, + "key_algorithm" TEXT NOT NULL, + "signature" TEXT NOT NULL, + FOREIGN KEY ("device_id") REFERENCES "devices" ("id") + ON DELETE CASCADE + UNIQUE(device_id, user_id, key_algorithm) + ); + + CREATE INDEX IF NOT EXISTS "device_keys_device_id" ON "device_keys" ("device_id"); + "#, + ) + .await?; + Ok(()) } @@ -473,19 +492,55 @@ impl SqliteStore { .await?; let keys: BTreeMap = key_rows - .iter() + .into_iter() .filter_map(|row| { - let algorithm: &str = &row.0; - let algorithm = KeyAlgorithm::try_from(algorithm).ok()?; - let key = &row.1; + let algorithm = KeyAlgorithm::try_from(&*row.0).ok()?; + let key = row.1; Some(( AlgorithmAndDeviceId(algorithm, device_id.as_str().into()), - key.to_owned(), + key, )) }) .collect(); + let signature_rows: Vec<(String, String, String)> = query_as( + "SELECT user_id, key_algorithm, signature + FROM device_signatures WHERE device_id = ?", + ) + .bind(device_row_id) + .fetch_all(&mut *connection) + .await?; + + let mut signatures: BTreeMap> = + BTreeMap::new(); + + for row in signature_rows { + let user_id = if let Ok(u) = UserId::try_from(&*row.0) { + u + } else { + continue; + }; + + let key_algorithm = if let Ok(k) = KeyAlgorithm::try_from(&*row.1) { + k + } else { + continue; + }; + + let signature = row.2; + + if !signatures.contains_key(&user_id) { + let _ = signatures.insert(user_id.clone(), BTreeMap::new()); + } + let user_map = signatures.get_mut(&user_id).unwrap(); + + user_map.insert( + AlgorithmAndDeviceId(key_algorithm, device_id.as_str().into()), + signature.to_owned(), + ); + } + let device = Device::new( user_id, device_id.as_str().into(), @@ -493,6 +548,7 @@ impl SqliteStore { trust_state, algorithms, keys, + signatures, ); store.add(device); @@ -562,6 +618,23 @@ impl SqliteStore { .await?; } + for (user_id, signature_map) in device.signatures() { + for (key_id, signature) in signature_map { + query( + "INSERT OR IGNORE INTO device_signatures ( + device_id, user_id, key_algorithm, signature + ) VALUES (?1, ?2, ?3, ?4) + ", + ) + .bind(device_row_id) + .bind(user_id.as_str()) + .bind(key_id.0.to_string()) + .bind(signature) + .execute(&mut *connection) + .await?; + } + } + Ok(()) } From 9ef784d66538337f8d44769028d265742524547c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 22 Jul 2020 09:27:43 +0200 Subject: [PATCH 83/83] crypto: Simplify the OlmMachine -> Device conversion. --- matrix_sdk_crypto/src/device.rs | 37 ++++---------------------------- matrix_sdk_crypto/src/machine.rs | 6 +++--- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/matrix_sdk_crypto/src/device.rs b/matrix_sdk_crypto/src/device.rs index bcd84fbd..93996b75 100644 --- a/matrix_sdk_crypto/src/device.rs +++ b/matrix_sdk_crypto/src/device.rs @@ -184,40 +184,11 @@ impl Device { pub(crate) fn mark_as_deleted(&self) { self.deleted.store(true, Ordering::Relaxed); } -} -#[cfg(test)] -impl From<&OlmMachine> for Device { - fn from(machine: &OlmMachine) -> Self { - let signatures = BTreeMap::new(); - - Device { - user_id: Arc::new(machine.user_id().clone()), - device_id: Arc::new(machine.device_id().into()), - algorithms: Arc::new(vec![ - Algorithm::MegolmV1AesSha2, - Algorithm::OlmV1Curve25519AesSha2, - ]), - keys: Arc::new( - machine - .identity_keys() - .iter() - .map(|(key, value)| { - ( - AlgorithmAndDeviceId( - KeyAlgorithm::try_from(key.as_ref()).unwrap(), - machine.device_id().into(), - ), - value.to_owned(), - ) - }) - .collect(), - ), - display_name: Arc::new(None), - deleted: Arc::new(AtomicBool::new(false)), - signatures: Arc::new(signatures), - trust_state: Arc::new(Atomic::new(TrustState::Unset)), - } + #[cfg(test)] + pub async fn from_machine(machine: &OlmMachine) -> Device { + let device_keys = machine.account.device_keys().await; + Device::try_from(&device_keys).unwrap() } } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 255685b2..c69ca24e 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -62,7 +62,7 @@ pub struct OlmMachine { /// The unique device id of the device that holds this account. device_id: Box, /// Our underlying Olm Account holding our identity keys. - account: Account, + pub(crate) account: Account, /// Store for the encryption keys. /// Persists all the encryption keys so a client can resume the session /// without the need to create new keys. @@ -1278,8 +1278,8 @@ mod test { let alice_device = alice_device_id(); let alice = OlmMachine::new(&alice_id, &alice_device); - let alice_deivce = Device::from(&alice); - let bob_device = Device::from(&bob); + let alice_deivce = Device::from_machine(&alice).await; + let bob_device = Device::from_machine(&bob).await; alice.store.save_devices(&[bob_device]).await.unwrap(); bob.store.save_devices(&[alice_deivce]).await.unwrap();