From c69d54e2d4ac4234d183aeda8515410c3ee17e71 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 10 Apr 2020 16:32:28 -0400 Subject: [PATCH 01/29] async_client: impl kick, join, leave, invite, create for --- src/async_client.rs | 167 ++++++++++++++++++++++++++++++++- src/lib.rs | 2 + src/request_builder.rs | 204 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/request_builder.rs diff --git a/src/async_client.rs b/src/async_client.rs index 9ce12c68..282e0c35 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -37,7 +37,7 @@ use ruma_api::{Endpoint, Outgoing}; use ruma_events::room::message::MessageEventContent; use ruma_events::EventResult; pub use ruma_events::EventType; -use ruma_identifiers::RoomId; +use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId}; #[cfg(feature = "encryption")] use ruma_identifiers::{DeviceId, UserId}; @@ -181,7 +181,16 @@ impl SyncSettings { use api::r0::client_exchange::send_event_to_device; #[cfg(feature = "encryption")] use api::r0::keys::{claim_keys, get_keys, upload_keys, KeyAlgorithm}; +use api::r0::membership::join_room_by_id; +use api::r0::membership::join_room_by_id_or_alias; +use api::r0::membership::kick_user; +use api::r0::membership::leave_room; +use api::r0::membership::{ + invite_user::{self, InvitationRecipient}, + Invite3pid, +}; use api::r0::message::create_message_event; +use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; @@ -333,6 +342,162 @@ impl AsyncClient { Ok(response) } + /// Join a room by `RoomId`. + /// + /// Returns a `join_room_by_id::Response` consisting of the + /// joined rooms `RoomId`. + /// + /// # Arguments + /// + /// * room_id - A valid RoomId otherwise sending will fail. + /// + pub async fn join_room_by_id(&mut self, room_id: &RoomId) -> Result { + let request = join_room_by_id::Request { + room_id: room_id.clone(), + third_party_signed: None, + }; + self.send(request).await + } + + /// Join a room by `RoomId`. + /// + /// Returns a `join_room_by_id_or_alias::Response` consisting of the + /// joined rooms `RoomId`. + /// + /// # Arguments + /// + /// * alias - A valid `RoomIdOrAliasId` otherwise sending will fail. + /// + pub async fn join_room_by_id_or_alias( + &mut self, + alias: &RoomIdOrAliasId, + ) -> Result { + let request = join_room_by_id_or_alias::Request { + room_id_or_alias: alias.clone(), + third_party_signed: None, + }; + self.send(request).await + } + + /// Kick a user out of the specified room. + /// + /// Returns a `kick_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId` otherwise sending will fail. + /// + /// * user_id - A valid `UserId`. + /// + /// * reason - Optional reason why the room member is being kicked out. + /// + pub async fn kick_user( + &mut self, + room_id: &RoomId, + user_id: &UserId, + reason: Option, + ) -> Result { + let request = kick_user::Request { + reason, + room_id: room_id.clone(), + user_id: user_id.clone(), + }; + self.send(request).await + } + + /// Leave the specified room. + /// + /// Returns a `leave_room::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + pub async fn leave_room(&mut self, room_id: &RoomId) -> Result { + let request = leave_room::Request { + room_id: room_id.clone(), + }; + self.send(request).await + } + + /// Invite the specified user by `UserId` to the given room. + /// + /// Returns a `invite_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + /// * user_id - A valid `UserId`. + /// + pub async fn invite_user_by_id( + &mut self, + room_id: &RoomId, + user_id: &UserId, + ) -> Result { + let request = invite_user::Request { + room_id: room_id.clone(), + recipient: InvitationRecipient::UserId { + user_id: user_id.clone(), + }, + }; + self.send(request).await + } + + /// Invite the specified user by third party id to the given room. + /// + /// Returns a `invite_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + /// * invite_id - A valid `UserId`. + /// + pub async fn invite_user_by_3pid( + &mut self, + room_id: &RoomId, + invite_id: &Invite3pid, + ) -> Result { + let request = invite_user::Request { + room_id: room_id.clone(), + recipient: InvitationRecipient::ThirdPartyId(invite_id.clone()), + }; + self.send(request).await + } + + /// A builder to create a room and send the request. + /// + /// Returns a `create_room::Response`, an empty response. + /// + /// # Arguments + /// + /// * room - the easiest way to create this request is using `RoomBuilder` struct. + /// + /// # Examples + /// ```ignore + /// use matrix_sdk::{AsyncClient, RoomBuilder}; + /// + /// let mut bldr = RoomBuilder::default(); + /// bldr.creation_content(false) + /// .initial_state(vec![]) + /// .visibility(Visibility::Public) + /// .name("name") + /// .room_version("v1.0"); + /// + /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + /// + /// assert!(cli.create_room(bldr).await.is_ok()); + /// ``` + /// + pub async fn create_room>( + &mut self, + room: R, + ) -> Result { + let request = room.into(); + self.send(request).await + } + /// Synchronize the client's state with the latest state on the server. /// /// # Arguments diff --git a/src/lib.rs b/src/lib.rs index 28bf9375..3cbf4379 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ mod base_client; mod error; mod event_emitter; mod models; +mod request_builder; mod session; #[cfg(test)] @@ -50,5 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; +pub use request_builder::RoomBuilder; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/request_builder.rs b/src/request_builder.rs new file mode 100644 index 00000000..aed4f55e --- /dev/null +++ b/src/request_builder.rs @@ -0,0 +1,204 @@ +use crate::events::room::power_levels::PowerLevelsEventContent; +use crate::identifiers::UserId; +use crate::api; +use api::r0::membership::Invite3pid; +use api::r0::room::{ + create_room::{self, CreationContent, InitialStateEvent, RoomPreset}, + Visibility, +}; + +/// A builder used to create rooms. +/// +/// # Examples +/// ``` +/// +/// ``` +#[derive(Default)] +pub struct RoomBuilder { + /// Extra keys to be added to the content of the `m.room.create`. + creation_content: Option, + /// List of state events to send to the new room. + /// + /// Takes precedence over events set by preset, but gets overriden by + /// name and topic keys. + initial_state: Vec, + /// A list of user IDs to invite to the room. + /// + /// This will tell the server to invite everyone in the list to the newly created room. + invite: Vec, + /// List of third party IDs of users to invite. + invite_3pid: Vec, + /// If set, this sets the `is_direct` flag on room invites. + is_direct: Option, + /// If this is included, an `m.room.name` event will be sent into the room to indicate + /// the name of the room. + name: Option, + /// Power level content to override in the default power level event. + power_level_content_override: Option, + /// Convenience parameter for setting various default state events based on a preset. + preset: Option, + /// The desired room alias local part. + room_alias_name: Option, + /// Room version to set for the room. Defaults to homeserver's default if not specified. + room_version: Option, + /// If this is included, an `m.room.topic` event will be sent into the room to indicate + /// the topic for the room. + topic: Option, + /// A public visibility indicates that the room will be shown in the published room + /// list. A private visibility will hide the room from the published room list. Rooms + /// default to private visibility if this key is not included. + visibility: Option, +} + +impl RoomBuilder { + /// Returns an empty `RoomBuilder` for creating rooms. + pub fn new() -> Self { + Self::default() + } + + /// Set the `CreationContent`. + /// + /// Weather users on other servers can join this room. + pub fn creation_content(&mut self, federate: bool) -> &mut Self { + let federate = Some(federate); + self.creation_content = Some(CreationContent { federate }); + self + } + + /// Set the `InitialStateEvent` vector. + /// + pub fn initial_state(&mut self, state: Vec) -> &mut Self { + self.initial_state = state; + self + } + + /// Set the vec of `UserId`s. + /// + pub fn invite(&mut self, invite: Vec) -> &mut Self { + self.invite = invite; + self + } + + /// Set the vec of `Invite3pid`s. + /// + pub fn invite_3pid(&mut self, invite: Vec) -> &mut Self { + self.invite_3pid = invite; + self + } + + /// Set the vec of `Invite3pid`s. + /// + pub fn is_direct(&mut self, direct: bool) -> &mut Self { + self.is_direct = Some(direct); + self + } + + /// Set the room name. A `m.room.name` event will be sent to the room. + /// + pub fn name>(&mut self, name: S) -> &mut Self { + self.name = Some(name.into()); + self + } + + /// Set the room's power levels. + /// + pub fn power_level_override(&mut self, power: PowerLevelsEventContent) -> &mut Self { + self.power_level_content_override = Some(power); + self + } + + /// Convenience for setting various default state events based on a preset. + /// + pub fn preset(&mut self, preset: RoomPreset) -> &mut Self { + self.preset = Some(preset); + self + } + + /// The local part of a room alias. + /// + pub fn room_alias_name>(&mut self, alias: S) -> &mut Self { + self.room_alias_name = Some(alias.into()); + self + } + + /// Room version, defaults to homeserver's version if left unspecified. + /// + pub fn room_version>(&mut self, version: S) -> &mut Self { + self.room_version = Some(version.into()); + self + } + + /// If included, a `m.room.topic` event will be sent to the room. + /// + pub fn topic>(&mut self, topic: S) -> &mut Self { + self.topic = Some(topic.into()); + self + } + + /// A public visibility indicates that the room will be shown in the published + /// room list. A private visibility will hide the room from the published room list. + /// Rooms default to private visibility if this key is not included. + /// + pub fn visibility(&mut self, vis: Visibility) -> &mut Self { + self.visibility = Some(vis); + self + } +} + +impl Into for RoomBuilder { + fn into(self) -> create_room::Request { + create_room::Request { + creation_content: self.creation_content, + initial_state: self.initial_state, + invite: self.invite, + invite_3pid: self.invite_3pid, + is_direct: self.is_direct, + name: self.name, + power_level_content_override: self.power_level_content_override, + preset: self.preset, + room_alias_name: self.room_alias_name, + room_version: self.room_version, + topic: self.topic, + visibility: self.visibility, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::{AsyncClient, Session}; + + use mockito::mock; + use url::Url; + + use std::convert::TryFrom; + + #[tokio::test] + async fn create_room_builder() { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + + let _m = mock("POST", "/_matrix/client/r0/createRoom") + .with_status(200) + .with_body_from_file("./tests/data/room_id.json") + .create(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let mut bldr = RoomBuilder::default(); + bldr.creation_content(false) + .initial_state(vec![]) + .visibility(Visibility::Public) + .name("name") + .room_version("v1.0"); + + let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + + assert!(cli.create_room(bldr).await.is_ok()); + } +} From 6358db94c707286517ad6fbbbf569a9e930390d7 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 10 Apr 2020 21:44:16 -0400 Subject: [PATCH 02/29] more docs --- src/async_client.rs | 1 - src/request_builder.rs | 22 ++++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 282e0c35..7557536c 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -486,7 +486,6 @@ impl AsyncClient { /// .room_version("v1.0"); /// /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - /// /// assert!(cli.create_room(bldr).await.is_ok()); /// ``` /// diff --git a/src/request_builder.rs b/src/request_builder.rs index aed4f55e..e174f8b7 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -11,9 +11,23 @@ use api::r0::room::{ /// /// # Examples /// ``` -/// +/// # use matrix_sdk::{AsyncClient, RoomBuilder}; +/// # use matrix_sdk::api::r0::room::Visibility; +/// # use url::Url; +/// # let homeserver = Url::parse("http://example.com").unwrap(); +/// let mut bldr = RoomBuilder::default(); +/// bldr.creation_content(false) +/// .initial_state(vec![]) +/// .visibility(Visibility::Public) +/// .name("name") +/// .room_version("v1.0"); +/// let mut cli = AsyncClient::new(homeserver, None).unwrap(); +/// # use futures::executor::block_on; +/// # block_on(async { +/// assert!(cli.create_room(bldr).await.is_err()); +/// # }) /// ``` -#[derive(Default)] +#[derive(Clone, Default)] pub struct RoomBuilder { /// Extra keys to be added to the content of the `m.room.create`. creation_content: Option, @@ -169,10 +183,8 @@ mod test { use super::*; use crate::{AsyncClient, Session}; - use mockito::mock; use url::Url; - use std::convert::TryFrom; #[tokio::test] @@ -196,9 +208,7 @@ mod test { .visibility(Visibility::Public) .name("name") .room_version("v1.0"); - let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - assert!(cli.create_room(bldr).await.is_ok()); } } From 7aeeeea4321f1bbc4a4a06542beba40342d9d378 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 11 Apr 2020 08:46:45 -0400 Subject: [PATCH 03/29] request_builder: test GetMessageBuilder --- src/async_client.rs | 33 ++++++- src/lib.rs | 2 +- src/request_builder.rs | 205 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 214 insertions(+), 26 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 7557536c..9d9635e0 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -40,7 +40,7 @@ pub use ruma_events::EventType; use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId}; #[cfg(feature = "encryption")] -use ruma_identifiers::{DeviceId, UserId}; +use ruma_identifiers::DeviceId; use crate::api; use crate::base_client::Client as BaseClient; @@ -190,6 +190,7 @@ use api::r0::membership::{ Invite3pid, }; use api::r0::message::create_message_event; +use api::r0::message::get_message_events; use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; @@ -472,7 +473,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room - the easiest way to create this request is using `RoomBuilder` struct. + /// * room - the easiest way to create this request is using the `RoomBuilder`. /// /// # Examples /// ```ignore @@ -497,6 +498,34 @@ impl AsyncClient { self.send(request).await } + /// Invite the specified user by third party id to the given room. + /// + /// Returns a `invite_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * request - The easiest way to create a `Request` is using the `GetMessageBuilder` + pub async fn get_message_events>( + &mut self, + request: R, + ) -> Result { + let req = request.into(); + let room_id = req.room_id.clone(); + let mut res = self.send(req).await?; + let mut client = self.base_client.write().await; + // TODO should we support this event? to keep emitting these msg events this is needed + for mut event in &mut res.chunk { + client + .receive_joined_timeline_event(&room_id, &mut event) + .await; + + if let EventResult::Ok(e) = event { + client.emit_timeline_event(&room_id, e).await; + } + } + Ok(res) + } + /// Synchronize the client's state with the latest state on the server. /// /// # Arguments diff --git a/src/lib.rs b/src/lib.rs index 3cbf4379..1e9412ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; -pub use request_builder::RoomBuilder; +pub use request_builder::{GetMessageBuilder, RoomBuilder}; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/request_builder.rs b/src/request_builder.rs index e174f8b7..1553dee8 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -1,20 +1,28 @@ -use crate::events::room::power_levels::PowerLevelsEventContent; -use crate::identifiers::UserId; use crate::api; +use crate::events::room::power_levels::PowerLevelsEventContent; +use crate::identifiers::{RoomId, UserId}; +use api::r0::filter::RoomEventFilter; use api::r0::membership::Invite3pid; +use api::r0::message::get_message_events::{self, Direction}; use api::r0::room::{ create_room::{self, CreationContent, InitialStateEvent, RoomPreset}, Visibility, }; +use js_int::UInt; + /// A builder used to create rooms. /// /// # Examples /// ``` +/// # use std::convert::TryFrom; /// # use matrix_sdk::{AsyncClient, RoomBuilder}; /// # use matrix_sdk::api::r0::room::Visibility; +/// # use matrix_sdk::identifiers::UserId; /// # use url::Url; /// # let homeserver = Url::parse("http://example.com").unwrap(); +/// # let mut rt = tokio::runtime::Runtime::new().unwrap(); +/// # rt.block_on(async { /// let mut bldr = RoomBuilder::default(); /// bldr.creation_content(false) /// .initial_state(vec![]) @@ -22,9 +30,7 @@ use api::r0::room::{ /// .name("name") /// .room_version("v1.0"); /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); -/// # use futures::executor::block_on; -/// # block_on(async { -/// assert!(cli.create_room(bldr).await.is_err()); +/// cli.create_room(bldr).await; /// # }) /// ``` #[derive(Clone, Default)] @@ -80,70 +86,60 @@ impl RoomBuilder { } /// Set the `InitialStateEvent` vector. - /// pub fn initial_state(&mut self, state: Vec) -> &mut Self { self.initial_state = state; self } /// Set the vec of `UserId`s. - /// pub fn invite(&mut self, invite: Vec) -> &mut Self { self.invite = invite; self } /// Set the vec of `Invite3pid`s. - /// pub fn invite_3pid(&mut self, invite: Vec) -> &mut Self { self.invite_3pid = invite; self } /// Set the vec of `Invite3pid`s. - /// pub fn is_direct(&mut self, direct: bool) -> &mut Self { self.is_direct = Some(direct); self } /// Set the room name. A `m.room.name` event will be sent to the room. - /// pub fn name>(&mut self, name: S) -> &mut Self { self.name = Some(name.into()); self } /// Set the room's power levels. - /// pub fn power_level_override(&mut self, power: PowerLevelsEventContent) -> &mut Self { self.power_level_content_override = Some(power); self } /// Convenience for setting various default state events based on a preset. - /// pub fn preset(&mut self, preset: RoomPreset) -> &mut Self { self.preset = Some(preset); self } /// The local part of a room alias. - /// pub fn room_alias_name>(&mut self, alias: S) -> &mut Self { self.room_alias_name = Some(alias.into()); self } /// Room version, defaults to homeserver's version if left unspecified. - /// pub fn room_version>(&mut self, version: S) -> &mut Self { self.room_version = Some(version.into()); self } /// If included, a `m.room.topic` event will be sent to the room. - /// pub fn topic>(&mut self, topic: S) -> &mut Self { self.topic = Some(topic.into()); self @@ -152,7 +148,6 @@ impl RoomBuilder { /// A public visibility indicates that the room will be shown in the published /// room list. A private visibility will hide the room from the published room list. /// Rooms default to private visibility if this key is not included. - /// pub fn visibility(&mut self, vis: Visibility) -> &mut Self { self.visibility = Some(vis); self @@ -178,14 +173,131 @@ impl Into for RoomBuilder { } } +/// Create a builder for making get_message_event requests. +#[derive(Clone, Default)] +pub struct GetMessageBuilder { + /// The room to get events from. + room_id: Option, + /// The token to start returning events from. + /// + /// This token can be obtained from a + /// prev_batch token returned for each room by the sync API, or from a start or end token + /// returned by a previous request to this endpoint. + from: Option, + /// The token to stop returning events at. + /// + /// This token can be obtained from a prev_batch + /// token returned for each room by the sync endpoint, or from a start or end token returned + /// by a previous request to this endpoint. + to: Option, + /// The direction to return events from. + direction: Option, + /// The maximum number of events to return. + /// + /// Default: 10. + limit: Option, + /// A filter of the returned events with. + filter: Option, +} + +impl GetMessageBuilder { + /// Create a `GetMessageBuilder` builder to make a `get_message_events::Request`. + /// + /// The `room_id` and `from`` fields **need to be set** to create the request. + /// + /// # Examples + /// ``` + /// # use matrix_sdk::{AsyncClient, GetMessageBuilder}; + /// # use matrix_sdk::api::r0::message::get_message_events::{self, Direction}; + /// # use matrix_sdk::identifiers::RoomId; + /// # use url::Url; + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); + /// # rt.block_on(async { + /// # let room_id = RoomId::new(homeserver.as_str()).unwrap(); + /// # let last_sync_token = "".to_string();; + /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); + /// + /// let mut bldr = GetMessageBuilder::new(); + /// bldr.room_id(room_id) + /// .from(last_sync_token) + /// .direction(Direction::Forward); + /// + /// cli.get_message_events(bldr).await.is_err(); + /// # }) + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// RoomId is required to create a `get_message_events::Request`. + pub fn room_id(&mut self, room_id: RoomId) -> &mut Self { + self.room_id = Some(room_id); + self + } + + /// A `next_batch` token or `start` or `end` from a previous `get_message_events` request. + /// + /// This is required to create a `get_message_events::Request`. + pub fn from(&mut self, from: String) -> &mut Self { + self.from = Some(from); + self + } + + /// A `next_batch` token or `start` or `end` from a previous `get_message_events` request. + /// + /// This token signals when to stop receiving events. + pub fn to(&mut self, to: String) -> &mut Self { + self.to = Some(to); + self + } + + /// The direction to return events from. + /// + /// If not specified `Direction::Backward` is used. + pub fn direction(&mut self, direction: Direction) -> &mut Self { + self.direction = Some(direction); + self + } + + /// The maximum number of events to return. + pub fn limit(&mut self, limit: UInt) -> &mut Self { + self.limit = Some(limit); + self + } + + /// Filter events by the given `RoomEventFilter`. + pub fn filter(&mut self, filter: RoomEventFilter) -> &mut Self { + self.filter = Some(filter); + self + } +} + +impl Into for GetMessageBuilder { + fn into(self) -> get_message_events::Request { + get_message_events::Request { + room_id: self.room_id.expect("`room_id` and `from` need to be set"), + from: self.from.expect("`room_id` and `from` need to be set"), + to: self.to, + dir: self.direction.unwrap_or(Direction::Backward), + limit: self.limit, + filter: self.filter, + } + } +} + #[cfg(test)] mod test { - use super::*; + use std::collections::HashMap; - use crate::{AsyncClient, Session}; - use mockito::mock; - use url::Url; + use super::*; + use crate::{AsyncClient, Session, identifiers::RoomId}; + use crate::events::room::power_levels::NotificationPowerLevels; + + use js_int::Int; + use mockito::{mock, Matcher}; use std::convert::TryFrom; + use url::Url; #[tokio::test] async fn create_room_builder() { @@ -202,13 +314,60 @@ mod test { device_id: "DEVICEID".to_owned(), }; - let mut bldr = RoomBuilder::default(); + let mut bldr = RoomBuilder::new(); bldr.creation_content(false) .initial_state(vec![]) .visibility(Visibility::Public) - .name("name") - .room_version("v1.0"); + .name("room_name") + .room_version("v1.0") + .invite_3pid(vec![]) + .is_direct(true) + .power_level_override(PowerLevelsEventContent { + ban: Int::max_value(), + events: HashMap::default(), + events_default: Int::min_value(), + invite: Int::min_value(), + kick: Int::min_value(), + redact: Int::max_value(), + state_default: Int::min_value(), + users_default: Int::min_value(), + notifications: NotificationPowerLevels { room: Int::min_value() }, + users: HashMap::default(), + }) + .preset(RoomPreset::PrivateChat) + .room_alias_name("room_alias") + .topic("room topic") + .visibility(Visibility::Private); let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); assert!(cli.create_room(bldr).await.is_ok()); } + + #[tokio::test] + async fn get_message_events() { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + + let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/messages".to_string())) + .with_status(200) + .with_body_from_file("./tests/data/room_messages.json") + .create(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let mut bldr = GetMessageBuilder::new(); + bldr + .room_id(RoomId::try_from("!roomid:example.com").unwrap()) + .from("t47429-4392820_219380_26003_2265".to_string()) + .to("t4357353_219380_26003_2265".to_string()) + .direction(Direction::Backward) + .limit(UInt::new(10).unwrap()); + // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? + // .filter(RoomEventFilter::default()); + + let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + assert!(cli.get_message_events(bldr).await.is_ok()); + } } From 5180f99a0e3ceb87ceb74cf6f4223e540898b724 Mon Sep 17 00:00:00 2001 From: Devin R Date: Sat, 11 Apr 2020 15:58:34 -0400 Subject: [PATCH 04/29] cargo fmt/update fixed olm --- src/request_builder.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/request_builder.rs b/src/request_builder.rs index 1553dee8..7eef70af 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -291,8 +291,8 @@ mod test { use std::collections::HashMap; use super::*; - use crate::{AsyncClient, Session, identifiers::RoomId}; use crate::events::room::power_levels::NotificationPowerLevels; + use crate::{identifiers::RoomId, AsyncClient, Session}; use js_int::Int; use mockito::{mock, Matcher}; @@ -331,7 +331,9 @@ mod test { redact: Int::max_value(), state_default: Int::min_value(), users_default: Int::min_value(), - notifications: NotificationPowerLevels { room: Int::min_value() }, + notifications: NotificationPowerLevels { + room: Int::min_value(), + }, users: HashMap::default(), }) .preset(RoomPreset::PrivateChat) @@ -346,10 +348,13 @@ mod test { async fn get_message_events() { let homeserver = Url::parse(&mockito::server_url()).unwrap(); - let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/messages".to_string())) - .with_status(200) - .with_body_from_file("./tests/data/room_messages.json") - .create(); + let _m = mock( + "GET", + Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/messages".to_string()), + ) + .with_status(200) + .with_body_from_file("./tests/data/room_messages.json") + .create(); let session = Session { access_token: "1234".to_owned(), @@ -358,14 +363,13 @@ mod test { }; let mut bldr = GetMessageBuilder::new(); - bldr - .room_id(RoomId::try_from("!roomid:example.com").unwrap()) + bldr.room_id(RoomId::try_from("!roomid:example.com").unwrap()) .from("t47429-4392820_219380_26003_2265".to_string()) .to("t4357353_219380_26003_2265".to_string()) .direction(Direction::Backward) .limit(UInt::new(10).unwrap()); - // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? - // .filter(RoomEventFilter::default()); + // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? + // .filter(RoomEventFilter::default()); let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); assert!(cli.get_message_events(bldr).await.is_ok()); From 0d8702b292a03b4846c51284f632fd9f5ba7c34d Mon Sep 17 00:00:00 2001 From: Devin R Date: Sun, 12 Apr 2020 08:21:23 -0400 Subject: [PATCH 05/29] rename get_message_events to room_messages --- src/async_client.rs | 2 +- src/lib.rs | 2 +- src/request_builder.rs | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 9d9635e0..7ef25b69 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -505,7 +505,7 @@ impl AsyncClient { /// # Arguments /// /// * request - The easiest way to create a `Request` is using the `GetMessageBuilder` - pub async fn get_message_events>( + pub async fn room_messages>( &mut self, request: R, ) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 1e9412ca..0e00b83e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; -pub use request_builder::{GetMessageBuilder, RoomBuilder}; +pub use request_builder::{RoomBuilder, RoomMessageBuilder}; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/request_builder.rs b/src/request_builder.rs index 7eef70af..9cdebe88 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -175,7 +175,7 @@ impl Into for RoomBuilder { /// Create a builder for making get_message_event requests. #[derive(Clone, Default)] -pub struct GetMessageBuilder { +pub struct RoomMessageBuilder { /// The room to get events from. room_id: Option, /// The token to start returning events from. @@ -200,14 +200,14 @@ pub struct GetMessageBuilder { filter: Option, } -impl GetMessageBuilder { - /// Create a `GetMessageBuilder` builder to make a `get_message_events::Request`. +impl RoomMessageBuilder { + /// Create a `RoomMessageBuilder` builder to make a `get_message_events::Request`. /// /// The `room_id` and `from`` fields **need to be set** to create the request. /// /// # Examples /// ``` - /// # use matrix_sdk::{AsyncClient, GetMessageBuilder}; + /// # use matrix_sdk::{AsyncClient, RoomMessageBuilder}; /// # use matrix_sdk::api::r0::message::get_message_events::{self, Direction}; /// # use matrix_sdk::identifiers::RoomId; /// # use url::Url; @@ -218,12 +218,12 @@ impl GetMessageBuilder { /// # let last_sync_token = "".to_string();; /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); /// - /// let mut bldr = GetMessageBuilder::new(); + /// let mut bldr = RoomMessageBuilder::new(); /// bldr.room_id(room_id) /// .from(last_sync_token) /// .direction(Direction::Forward); /// - /// cli.get_message_events(bldr).await.is_err(); + /// cli.room_messages(bldr).await.is_err(); /// # }) /// ``` pub fn new() -> Self { @@ -273,7 +273,7 @@ impl GetMessageBuilder { } } -impl Into for GetMessageBuilder { +impl Into for RoomMessageBuilder { fn into(self) -> get_message_events::Request { get_message_events::Request { room_id: self.room_id.expect("`room_id` and `from` need to be set"), @@ -362,7 +362,7 @@ mod test { device_id: "DEVICEID".to_owned(), }; - let mut bldr = GetMessageBuilder::new(); + let mut bldr = RoomMessageBuilder::new(); bldr.room_id(RoomId::try_from("!roomid:example.com").unwrap()) .from("t47429-4392820_219380_26003_2265".to_string()) .to("t4357353_219380_26003_2265".to_string()) @@ -372,6 +372,6 @@ mod test { // .filter(RoomEventFilter::default()); let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - assert!(cli.get_message_events(bldr).await.is_ok()); + assert!(cli.room_messages(bldr).await.is_ok()); } } From fb10e9bf8788db4195c8a385ab9b1ce219d7b3de Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 13 Apr 2020 14:08:51 -0400 Subject: [PATCH 06/29] add optional txn_id to room_send, add docs to room_messages --- src/async_client.rs | 49 ++++++++++++++++++++++++------------------ src/request_builder.rs | 24 ++++++++++----------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 7ef25b69..13037a12 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -479,15 +479,15 @@ impl AsyncClient { /// ```ignore /// use matrix_sdk::{AsyncClient, RoomBuilder}; /// - /// let mut bldr = RoomBuilder::default(); - /// bldr.creation_content(false) + /// let mut builder = RoomBuilder::default(); + /// builder.creation_content(false) /// .initial_state(vec![]) /// .visibility(Visibility::Public) /// .name("name") /// .room_version("v1.0"); /// /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - /// assert!(cli.create_room(bldr).await.is_ok()); + /// assert!(cli.create_room(builder).await.is_ok()); /// ``` /// pub async fn create_room>( @@ -505,25 +505,27 @@ impl AsyncClient { /// # Arguments /// /// * request - The easiest way to create a `Request` is using the `GetMessageBuilder` + /// + /// # Examples + /// ```ignore + /// use matrix_sdk::{AsyncClient, RoomBuilder}; + /// + /// let mut builder = RoomMessageBuilder::new(); + /// builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) + /// .from("t47429-4392820_219380_26003_2265".to_string()) + /// .to("t4357353_219380_26003_2265".to_string()) + /// .direction(Direction::Backward) + /// .limit(UInt::new(10).unwrap()); + /// + /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + /// assert!(cli.create_room(builder).await.is_ok()); + /// ``` pub async fn room_messages>( &mut self, request: R, ) -> Result { let req = request.into(); - let room_id = req.room_id.clone(); - let mut res = self.send(req).await?; - let mut client = self.base_client.write().await; - // TODO should we support this event? to keep emitting these msg events this is needed - for mut event in &mut res.chunk { - client - .receive_joined_timeline_event(&room_id, &mut event) - .await; - - if let EventResult::Ok(e) = event { - client.emit_timeline_event(&room_id, e).await; - } - } - Ok(res) + self.send(req).await } /// Synchronize the client's state with the latest state on the server. @@ -827,6 +829,9 @@ impl AsyncClient { /// * `room_id` - The id of the room that should receive the message. /// /// * `content` - The content of the message event. + /// + /// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent` held + /// in it's unsigned field as `transaction_id`. /// /// # Example /// ```no_run @@ -842,21 +847,23 @@ impl AsyncClient { /// # let homeserver = Url::parse("http://localhost:8080").unwrap(); /// # let mut client = AsyncClient::new(homeserver, None).unwrap(); /// # let room_id = RoomId::try_from("!test:localhost").unwrap(); - /// + /// use uuid::Uuid; + /// /// let content = MessageEventContent::Text(TextMessageEventContent { /// body: "Hello world".to_owned(), /// format: None, /// formatted_body: None, /// relates_to: None, /// }); - /// - /// client.room_send(&room_id, content).await.unwrap(); + /// let txn_id = Uuid::new_v4(); + /// client.room_send(&room_id, content, Some(uuid)).await.unwrap(); /// }) /// ``` pub async fn room_send( &mut self, room_id: &RoomId, #[allow(unused_mut)] mut content: MessageEventContent, + txn_id: Option, ) -> Result { #[allow(unused_mut)] let mut event_type = EventType::RoomMessage; @@ -915,7 +922,7 @@ impl AsyncClient { let request = create_message_event::Request { room_id: room_id.clone(), event_type, - txn_id: Uuid::new_v4().to_string(), + txn_id: txn_id.unwrap_or(Uuid::new_v4()).to_string(), data: content, }; diff --git a/src/request_builder.rs b/src/request_builder.rs index 9cdebe88..05b61e33 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -23,14 +23,14 @@ use js_int::UInt; /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); /// # rt.block_on(async { -/// let mut bldr = RoomBuilder::default(); -/// bldr.creation_content(false) +/// let mut builder = RoomBuilder::default(); +/// builder.creation_content(false) /// .initial_state(vec![]) /// .visibility(Visibility::Public) /// .name("name") /// .room_version("v1.0"); /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); -/// cli.create_room(bldr).await; +/// cli.create_room(builder).await; /// # }) /// ``` #[derive(Clone, Default)] @@ -218,12 +218,12 @@ impl RoomMessageBuilder { /// # let last_sync_token = "".to_string();; /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); /// - /// let mut bldr = RoomMessageBuilder::new(); - /// bldr.room_id(room_id) + /// let mut builder = RoomMessageBuilder::new(); + /// builder.room_id(room_id) /// .from(last_sync_token) /// .direction(Direction::Forward); /// - /// cli.room_messages(bldr).await.is_err(); + /// cli.room_messages(builder).await.is_err(); /// # }) /// ``` pub fn new() -> Self { @@ -314,8 +314,8 @@ mod test { device_id: "DEVICEID".to_owned(), }; - let mut bldr = RoomBuilder::new(); - bldr.creation_content(false) + let mut builder = RoomBuilder::new(); + builder.creation_content(false) .initial_state(vec![]) .visibility(Visibility::Public) .name("room_name") @@ -341,7 +341,7 @@ mod test { .topic("room topic") .visibility(Visibility::Private); let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - assert!(cli.create_room(bldr).await.is_ok()); + assert!(cli.create_room(builder).await.is_ok()); } #[tokio::test] @@ -362,8 +362,8 @@ mod test { device_id: "DEVICEID".to_owned(), }; - let mut bldr = RoomMessageBuilder::new(); - bldr.room_id(RoomId::try_from("!roomid:example.com").unwrap()) + let mut builder = RoomMessageBuilder::new(); + builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) .from("t47429-4392820_219380_26003_2265".to_string()) .to("t4357353_219380_26003_2265".to_string()) .direction(Direction::Backward) @@ -372,6 +372,6 @@ mod test { // .filter(RoomEventFilter::default()); let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - assert!(cli.room_messages(bldr).await.is_ok()); + assert!(cli.room_messages(builder).await.is_ok()); } } From 1a7856e9fe0b223db7df610234aae504bc330ef3 Mon Sep 17 00:00:00 2001 From: Devin R Date: Mon, 13 Apr 2020 14:46:54 -0400 Subject: [PATCH 07/29] fix room_send example, cargo fmt/clippy --- src/async_client.rs | 15 ++++++++------- src/request_builder.rs | 6 ++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 13037a12..a7203347 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -505,18 +505,18 @@ impl AsyncClient { /// # Arguments /// /// * request - The easiest way to create a `Request` is using the `GetMessageBuilder` - /// + /// /// # Examples /// ```ignore /// use matrix_sdk::{AsyncClient, RoomBuilder}; - /// + /// /// let mut builder = RoomMessageBuilder::new(); /// builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) /// .from("t47429-4392820_219380_26003_2265".to_string()) /// .to("t4357353_219380_26003_2265".to_string()) /// .direction(Direction::Backward) /// .limit(UInt::new(10).unwrap()); - /// + /// /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); /// assert!(cli.create_room(builder).await.is_ok()); /// ``` @@ -829,9 +829,10 @@ impl AsyncClient { /// * `room_id` - The id of the room that should receive the message. /// /// * `content` - The content of the message event. - /// + /// /// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent` held - /// in it's unsigned field as `transaction_id`. + /// in it's unsigned field as `transaction_id`. If not given one is created for the + /// message. /// /// # Example /// ```no_run @@ -848,7 +849,7 @@ impl AsyncClient { /// # let mut client = AsyncClient::new(homeserver, None).unwrap(); /// # let room_id = RoomId::try_from("!test:localhost").unwrap(); /// use uuid::Uuid; - /// + /// /// let content = MessageEventContent::Text(TextMessageEventContent { /// body: "Hello world".to_owned(), /// format: None, @@ -856,7 +857,7 @@ impl AsyncClient { /// relates_to: None, /// }); /// let txn_id = Uuid::new_v4(); - /// client.room_send(&room_id, content, Some(uuid)).await.unwrap(); + /// client.room_send(&room_id, content, Some(txn_id)).await.unwrap(); /// }) /// ``` pub async fn room_send( diff --git a/src/request_builder.rs b/src/request_builder.rs index 05b61e33..edf32721 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -315,7 +315,8 @@ mod test { }; let mut builder = RoomBuilder::new(); - builder.creation_content(false) + builder + .creation_content(false) .initial_state(vec![]) .visibility(Visibility::Public) .name("room_name") @@ -363,7 +364,8 @@ mod test { }; let mut builder = RoomMessageBuilder::new(); - builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) + builder + .room_id(RoomId::try_from("!roomid:example.com").unwrap()) .from("t47429-4392820_219380_26003_2265".to_string()) .to("t4357353_219380_26003_2265".to_string()) .direction(Direction::Backward) From e64d07340275e238ba099fd9f9701909f4d4f196 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 06:36:03 -0400 Subject: [PATCH 08/29] rename RoomMessageBuilder, make examp build, expand on docs --- src/async_client.rs | 76 +++++++++++++++++++++++++----------------- src/lib.rs | 2 +- src/request_builder.rs | 54 +++++++++++++++--------------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index a7203347..547009ae 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -350,8 +350,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - A valid RoomId otherwise sending will fail. - /// + /// * room_id - The `RoomId` of the room to be joined. pub async fn join_room_by_id(&mut self, room_id: &RoomId) -> Result { let request = join_room_by_id::Request { room_id: room_id.clone(), @@ -367,8 +366,8 @@ impl AsyncClient { /// /// # Arguments /// - /// * alias - A valid `RoomIdOrAliasId` otherwise sending will fail. - /// + /// * alias - The `RoomId` or `RoomAliasId` of the room to be joined. + /// An alias looks like this `#name:example.com` pub async fn join_room_by_id_or_alias( &mut self, alias: &RoomIdOrAliasId, @@ -386,12 +385,11 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - A valid `RoomId` otherwise sending will fail. + /// * room_id - The `RoomId` of the room the user should be kicked out of. /// - /// * user_id - A valid `UserId`. + /// * user_id - The `UserId` of the user that should be kicked out of the room. /// /// * reason - Optional reason why the room member is being kicked out. - /// pub async fn kick_user( &mut self, room_id: &RoomId, @@ -412,7 +410,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - A valid `RoomId`. + /// * room_id - The `RoomId` of the room to leave. /// pub async fn leave_room(&mut self, room_id: &RoomId) -> Result { let request = leave_room::Request { @@ -427,10 +425,9 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - A valid `RoomId`. - /// - /// * user_id - A valid `UserId`. + /// * room_id - The `RoomId` of the room to invite the specified user to. /// + /// * user_id - The `UserId` of the user to invite to the room. pub async fn invite_user_by_id( &mut self, room_id: &RoomId, @@ -451,10 +448,9 @@ impl AsyncClient { /// /// # Arguments /// - /// * room_id - A valid `RoomId`. - /// - /// * invite_id - A valid `UserId`. + /// * room_id - The `RoomId` of the room to invite the specified user to. /// + /// * invite_id - A third party id of a user to invite to the room. pub async fn invite_user_by_3pid( &mut self, room_id: &RoomId, @@ -467,18 +463,22 @@ impl AsyncClient { self.send(request).await } - /// A builder to create a room and send the request. + /// Create a room using the `RoomBuilder` and send the request. /// - /// Returns a `create_room::Response`, an empty response. + /// Sends a request to `/_matrix/client/r0/createRoom`, returns a `create_room::Response`, + /// this is an empty response. /// /// # Arguments /// - /// * room - the easiest way to create this request is using the `RoomBuilder`. + /// * room - The easiest way to create this request is using the `RoomBuilder`. /// /// # Examples - /// ```ignore + /// ```no_run /// use matrix_sdk::{AsyncClient, RoomBuilder}; - /// + /// # use matrix_sdk::api::r0::room::Visibility; + /// # use url::Url; + /// + /// # let homeserver = Url::parse("http://example.com").unwrap(); /// let mut builder = RoomBuilder::default(); /// builder.creation_content(false) /// .initial_state(vec![]) @@ -486,10 +486,12 @@ impl AsyncClient { /// .name("name") /// .room_version("v1.0"); /// - /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); + /// # use futures::executor::block_on; + /// # block_on(async { /// assert!(cli.create_room(builder).await.is_ok()); + /// # }); /// ``` - /// pub async fn create_room>( &mut self, room: R, @@ -498,27 +500,41 @@ impl AsyncClient { self.send(request).await } - /// Invite the specified user by third party id to the given room. + /// Get messages starting at a specific sync point using the + /// `MessagesRequestBuilder`s `from` field as a starting point. /// - /// Returns a `invite_user::Response`, an empty response. + /// Sends a request to `/_matrix/client/r0/rooms/{room_id}/messages` and + /// returns a `get_message_events::IncomingResponse` that contains chunks + /// of `RoomEvents`. /// /// # Arguments /// - /// * request - The easiest way to create a `Request` is using the `GetMessageBuilder` + /// * request - The easiest way to create a `Request` is using the + /// `MessagesRequestBuilder`. /// /// # Examples - /// ```ignore - /// use matrix_sdk::{AsyncClient, RoomBuilder}; - /// - /// let mut builder = RoomMessageBuilder::new(); + /// ```no_run + /// # use std::convert::TryFrom; + /// use matrix_sdk::{AsyncClient, MessagesRequestBuilder}; + /// # use matrix_sdk::identifiers::RoomId; + /// # use matrix_sdk::api::r0::filter::RoomEventFilter; + /// # use matrix_sdk::api::r0::message::get_message_events::Direction; + /// # use url::Url; + /// # use js_int::UInt; + /// + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// let mut builder = MessagesRequestBuilder::new(); /// builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) /// .from("t47429-4392820_219380_26003_2265".to_string()) /// .to("t4357353_219380_26003_2265".to_string()) /// .direction(Direction::Backward) /// .limit(UInt::new(10).unwrap()); /// - /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); - /// assert!(cli.create_room(builder).await.is_ok()); + /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); + /// # use futures::executor::block_on; + /// # block_on(async { + /// assert!(cli.room_messages(builder).await.is_ok()); + /// # }); /// ``` pub async fn room_messages>( &mut self, diff --git a/src/lib.rs b/src/lib.rs index 0e00b83e..de536d50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; -pub use request_builder::{RoomBuilder, RoomMessageBuilder}; +pub use request_builder::{RoomBuilder, MessagesRequestBuilder}; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/request_builder.rs b/src/request_builder.rs index edf32721..42f088f5 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -174,8 +174,30 @@ impl Into for RoomBuilder { } /// Create a builder for making get_message_event requests. +/// +/// # Examples +/// ``` +/// # use matrix_sdk::{AsyncClient, MessagesRequestBuilder}; +/// # use matrix_sdk::api::r0::message::get_message_events::{self, Direction}; +/// # use matrix_sdk::identifiers::RoomId; +/// # use url::Url; +/// # let homeserver = Url::parse("http://example.com").unwrap(); +/// # let mut rt = tokio::runtime::Runtime::new().unwrap(); +/// # rt.block_on(async { +/// # let room_id = RoomId::new(homeserver.as_str()).unwrap(); +/// # let last_sync_token = "".to_string();; +/// let mut cli = AsyncClient::new(homeserver, None).unwrap(); +/// +/// let mut builder = MessagesRequestBuilder::new(); +/// builder.room_id(room_id) +/// .from(last_sync_token) +/// .direction(Direction::Forward); +/// +/// cli.room_messages(builder).await.is_err(); +/// # }) +/// ``` #[derive(Clone, Default)] -pub struct RoomMessageBuilder { +pub struct MessagesRequestBuilder { /// The room to get events from. room_id: Option, /// The token to start returning events from. @@ -200,32 +222,10 @@ pub struct RoomMessageBuilder { filter: Option, } -impl RoomMessageBuilder { - /// Create a `RoomMessageBuilder` builder to make a `get_message_events::Request`. +impl MessagesRequestBuilder { + /// Create a `MessagesRequestBuilder` builder to make a `get_message_events::Request`. /// /// The `room_id` and `from`` fields **need to be set** to create the request. - /// - /// # Examples - /// ``` - /// # use matrix_sdk::{AsyncClient, RoomMessageBuilder}; - /// # use matrix_sdk::api::r0::message::get_message_events::{self, Direction}; - /// # use matrix_sdk::identifiers::RoomId; - /// # use url::Url; - /// # let homeserver = Url::parse("http://example.com").unwrap(); - /// # let mut rt = tokio::runtime::Runtime::new().unwrap(); - /// # rt.block_on(async { - /// # let room_id = RoomId::new(homeserver.as_str()).unwrap(); - /// # let last_sync_token = "".to_string();; - /// let mut cli = AsyncClient::new(homeserver, None).unwrap(); - /// - /// let mut builder = RoomMessageBuilder::new(); - /// builder.room_id(room_id) - /// .from(last_sync_token) - /// .direction(Direction::Forward); - /// - /// cli.room_messages(builder).await.is_err(); - /// # }) - /// ``` pub fn new() -> Self { Self::default() } @@ -273,7 +273,7 @@ impl RoomMessageBuilder { } } -impl Into for RoomMessageBuilder { +impl Into for MessagesRequestBuilder { fn into(self) -> get_message_events::Request { get_message_events::Request { room_id: self.room_id.expect("`room_id` and `from` need to be set"), @@ -363,7 +363,7 @@ mod test { device_id: "DEVICEID".to_owned(), }; - let mut builder = RoomMessageBuilder::new(); + let mut builder = MessagesRequestBuilder::new(); builder .room_id(RoomId::try_from("!roomid:example.com").unwrap()) .from("t47429-4392820_219380_26003_2265".to_string()) From 2cfaf64febf9acfb34c8c8564eed12d46266c183 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 06:42:42 -0400 Subject: [PATCH 09/29] cargo fmt/clippy --- src/async_client.rs | 8 ++++---- src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 547009ae..718e423b 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -477,7 +477,7 @@ impl AsyncClient { /// use matrix_sdk::{AsyncClient, RoomBuilder}; /// # use matrix_sdk::api::r0::room::Visibility; /// # use url::Url; - /// + /// /// # let homeserver = Url::parse("http://example.com").unwrap(); /// let mut builder = RoomBuilder::default(); /// builder.creation_content(false) @@ -509,7 +509,7 @@ impl AsyncClient { /// /// # Arguments /// - /// * request - The easiest way to create a `Request` is using the + /// * request - The easiest way to create a `Request` is using the /// `MessagesRequestBuilder`. /// /// # Examples @@ -521,7 +521,7 @@ impl AsyncClient { /// # use matrix_sdk::api::r0::message::get_message_events::Direction; /// # use url::Url; /// # use js_int::UInt; - /// + /// /// # let homeserver = Url::parse("http://example.com").unwrap(); /// let mut builder = MessagesRequestBuilder::new(); /// builder.room_id(RoomId::try_from("!roomid:example.com").unwrap()) @@ -939,7 +939,7 @@ impl AsyncClient { let request = create_message_event::Request { room_id: room_id.clone(), event_type, - txn_id: txn_id.unwrap_or(Uuid::new_v4()).to_string(), + txn_id: txn_id.unwrap_or_else(Uuid::new_v4).to_string(), data: content, }; diff --git a/src/lib.rs b/src/lib.rs index de536d50..b3c13bdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; -pub use request_builder::{RoomBuilder, MessagesRequestBuilder}; +pub use request_builder::{MessagesRequestBuilder, RoomBuilder}; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); From f857fbb25bdefa7597ab27e48b8e483da9ef8f4b Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 07:19:58 -0400 Subject: [PATCH 10/29] remove unused sync2 json, pre-commit all files --- design.md | 2 +- tests/data/events/room_avatar.json | 1 - tests/data/sync2.json | 160 ----------------------------- 3 files changed, 1 insertion(+), 162 deletions(-) delete mode 100644 tests/data/sync2.json diff --git a/design.md b/design.md index 8b974294..2686f513 100644 --- a/design.md +++ b/design.md @@ -3,7 +3,7 @@ ## Design and Layout #### Async Client -The highest level structure that ties the other pieces of functionality together. The client is responsible for the Request/Response cycle. It can be thought of as a thin layer atop the `BaseClient` passing requests along for the `BaseClient` to handle. A user should be able to write their own `AsyncClient` using the `BaseClient`. It knows how to +The highest level structure that ties the other pieces of functionality together. The client is responsible for the Request/Response cycle. It can be thought of as a thin layer atop the `BaseClient` passing requests along for the `BaseClient` to handle. A user should be able to write their own `AsyncClient` using the `BaseClient`. It knows how to - login - send messages - encryption ... diff --git a/tests/data/events/room_avatar.json b/tests/data/events/room_avatar.json index b92f9161..92ed2519 100644 --- a/tests/data/events/room_avatar.json +++ b/tests/data/events/room_avatar.json @@ -18,4 +18,3 @@ "age": 1234 } } - diff --git a/tests/data/sync2.json b/tests/data/sync2.json deleted file mode 100644 index 6adb2c13..00000000 --- a/tests/data/sync2.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "next_batch": "s72595_4483_1934", - "presence": { - "events": [ - { - "content": { - "avatar_url": "mxc://localhost:wefuiwegh8742w", - "last_active_ago": 2478593, - "presence": "online", - "currently_active": false, - "status_msg": "Making cupcakes" - }, - "type": "m.presence", - "sender": "@example:localhost" - } - ] - }, - "account_data": { - "events": [ - { - "type": "org.example.custom.config", - "content": { - "custom_config_key": "custom_config_value" - } - } - ] - }, - "rooms": { - "join": { - "!726s6s6q:example.com": { - "summary": { - "m.heroes": [ - "@alice:example.com", - "@bob:example.com" - ], - "m.joined_member_count": 2, - "m.invited_member_count": 0 - }, - "state": { - "events": [ - { - "content": { - "membership": "join", - "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", - "displayname": "Alice Margatroid" - }, - "type": "m.room.member", - "event_id": "$143273582443PhrSn:example.org", - "room_id": "!726s6s6q:example.com", - "sender": "@example:example.org", - "origin_server_ts": 1432735824653, - "unsigned": { - "age": 1234 - }, - "state_key": "@alice:example.org" - } - ] - }, - "timeline": { - "events": [ - { - "content": { - "membership": "join", - "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", - "displayname": "Alice Margatroid" - }, - "type": "m.room.member", - "event_id": "$143273582443PhrSn:example.org", - "room_id": "!726s6s6q:example.com", - "sender": "@example:example.org", - "origin_server_ts": 1432735824653, - "unsigned": { - "age": 1234 - }, - "state_key": "@alice:example.org" - }, - { - "content": { - "body": "This is an example text message", - "msgtype": "m.text", - "format": "org.matrix.custom.html", - "formatted_body": "This is an example text message" - }, - "type": "m.room.message", - "event_id": "$143273582443PhrSn:example.org", - "room_id": "!726s6s6q:example.com", - "sender": "@example:example.org", - "origin_server_ts": 1432735824653, - "unsigned": { - "age": 1234 - } - } - ], - "limited": true, - "prev_batch": "t34-23535_0_0" - }, - "ephemeral": { - "events": [ - { - "content": { - "user_ids": [ - "@alice:matrix.org", - "@bob:example.com" - ] - }, - "type": "m.typing", - "room_id": "!jEsUZKDJdhlrceRyVU:example.org" - } - ] - }, - "account_data": { - "events": [ - { - "content": { - "tags": { - "u.work": { - "order": 0.9 - } - } - }, - "type": "m.tag" - }, - { - "type": "org.example.custom.room.config", - "content": { - "custom_config_key": "custom_config_value" - } - } - ] - } - } - }, - "invite": { - "!696r7674:example.com": { - "invite_state": { - "events": [ - { - "sender": "@alice:example.com", - "type": "m.room.name", - "state_key": "", - "content": { - "name": "My Room Name" - } - }, - { - "sender": "@alice:example.com", - "type": "m.room.member", - "state_key": "@bob:example.com", - "content": { - "membership": "invite" - } - } - ] - } - } - }, - "leave": {} - } - } - \ No newline at end of file From 25e60d398be4ffd9259a1bbd871a241e5e42f59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 14 Apr 2020 14:05:18 +0200 Subject: [PATCH 11/29] crypto: Move the session mutex into the Session struct. --- src/crypto/machine.rs | 16 +++----- src/crypto/memory_stores.rs | 15 ++++--- src/crypto/olm.rs | 71 +++++++++++++++++++-------------- src/crypto/store/memorystore.rs | 7 +--- src/crypto/store/mod.rs | 7 +--- src/crypto/store/sqlite.rs | 39 +++++++----------- 6 files changed, 73 insertions(+), 82 deletions(-) diff --git a/src/crypto/machine.rs b/src/crypto/machine.rs index d3fe2972..703f6836 100644 --- a/src/crypto/machine.rs +++ b/src/crypto/machine.rs @@ -18,7 +18,6 @@ use std::mem; #[cfg(feature = "sqlite-cryptostore")] use std::path::Path; use std::result::Result as StdResult; -use std::sync::Arc; use uuid::Uuid; use super::error::{OlmError, Result, SignatureError, VerificationResult}; @@ -34,7 +33,6 @@ use api::r0::keys; use cjson; use olm_rs::{session::OlmMessage, utility::OlmUtility}; use serde_json::{json, Value}; -use tokio::sync::Mutex; use tracing::{debug, error, info, instrument, trace, warn}; use ruma_client_api::r0::client_exchange::{ @@ -658,19 +656,17 @@ impl OlmMachine { return Ok(None); }; - for session in &*sessions.lock().await { + for session in &mut *sessions.lock().await { let mut matches = false; - let mut session_lock = session.lock().await; - if let OlmMessage::PreKey(m) = &message { - matches = session_lock.matches(sender_key, m.clone())?; + matches = session.matches(sender_key, m.clone()).await?; if !matches { continue; } } - let ret = session_lock.decrypt(message.clone()); + let ret = session.decrypt(message.clone()).await; if let Ok(p) = ret { self.store.save_session(session.clone()).await?; @@ -706,7 +702,7 @@ impl OlmMachine { } }; - let plaintext = session.decrypt(message)?; + let plaintext = session.decrypt(message).await?; self.store.add_and_save_session(session).await?; plaintext }; @@ -861,7 +857,7 @@ impl OlmMachine { async fn olm_encrypt( &mut self, - session: Arc>, + mut session: Session, recipient_device: &Device, event_type: EventType, content: Value, @@ -892,7 +888,7 @@ impl OlmMachine { let plaintext = cjson::to_string(&payload) .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", payload))); - let ciphertext = session.lock().await.encrypt(&plaintext).to_tuple(); + let ciphertext = session.encrypt(&plaintext).await.to_tuple(); self.store.save_session(session).await?; let message_type: usize = ciphertext.0.into(); diff --git a/src/crypto/memory_stores.rs b/src/crypto/memory_stores.rs index 89704ad1..413e1e06 100644 --- a/src/crypto/memory_stores.rs +++ b/src/crypto/memory_stores.rs @@ -24,7 +24,7 @@ use crate::identifiers::{DeviceId, RoomId, UserId}; #[derive(Debug)] pub struct SessionStore { - entries: HashMap>>>>>, + entries: HashMap>>>, } impl SessionStore { @@ -34,25 +34,24 @@ impl SessionStore { } } - pub async fn add(&mut self, session: Session) -> Arc> { - if !self.entries.contains_key(&session.sender_key) { + pub async fn add(&mut self, session: Session) -> Session { + if !self.entries.contains_key(&*session.sender_key) { self.entries.insert( - session.sender_key.to_owned(), + session.sender_key.to_string(), Arc::new(Mutex::new(Vec::new())), ); } - let sessions = self.entries.get_mut(&session.sender_key).unwrap(); - let session = Arc::new(Mutex::new(session)); + let sessions = self.entries.get_mut(&*session.sender_key).unwrap(); sessions.lock().await.push(session.clone()); session } - pub fn get(&self, sender_key: &str) -> Option>>>>> { + pub fn get(&self, sender_key: &str) -> Option>>> { self.entries.get(sender_key).cloned() } - pub fn set_for_sender(&mut self, sender_key: &str, sessions: Vec>>) { + pub fn set_for_sender(&mut self, sender_key: &str, sessions: Vec) { self.entries .insert(sender_key.to_owned(), Arc::new(Mutex::new(sessions))); } diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index bf5e69ea..1fed7697 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::fmt; +use std::mem; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Instant; @@ -170,12 +171,14 @@ impl Account { .create_outbound_session(their_identity_key, &their_one_time_key.key)?; let now = Instant::now(); + let session_id = session.session_id(); Ok(Session { - inner: session, - sender_key: their_identity_key.to_owned(), - creation_time: now.clone(), - last_use_time: now, + 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.clone()), + last_use_time: Arc::new(now), }) } @@ -209,12 +212,14 @@ impl Account { ); let now = Instant::now(); + let session_id = session.session_id(); Ok(Session { - inner: session, - sender_key: their_identity_key.to_owned(), - creation_time: now.clone(), - last_use_time: now, + 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.clone()), + last_use_time: Arc::new(now), }) } } @@ -225,16 +230,17 @@ impl PartialEq for Account { } } -#[derive(Debug)] /// The Olm Session. /// /// Sessions are used to exchange encrypted messages between two /// accounts/devices. +#[derive(Debug, Clone)] pub struct Session { - inner: OlmSession, - pub(crate) sender_key: String, - pub(crate) creation_time: Instant, - pub(crate) last_use_time: Instant, + inner: Arc>, + session_id: Arc, + pub(crate) sender_key: Arc, + pub(crate) creation_time: Arc, + pub(crate) last_use_time: Arc, } impl Session { @@ -246,9 +252,9 @@ impl Session { /// # Arguments /// /// * `message` - The Olm message that should be decrypted. - pub fn decrypt(&mut self, message: OlmMessage) -> Result { - let plaintext = self.inner.decrypt(message)?; - self.last_use_time = Instant::now(); + pub async fn decrypt(&mut self, message: OlmMessage) -> Result { + let plaintext = self.inner.lock().await.decrypt(message)?; + mem::replace(&mut self.last_use_time, Arc::new(Instant::now())); Ok(plaintext) } @@ -259,9 +265,9 @@ impl Session { /// # Arguments /// /// * `plaintext` - The plaintext that should be encrypted. - pub fn encrypt(&mut self, plaintext: &str) -> OlmMessage { - let message = self.inner.encrypt(plaintext); - self.last_use_time = Instant::now(); + pub async fn encrypt(&mut self, plaintext: &str) -> OlmMessage { + let message = self.inner.lock().await.encrypt(plaintext); + mem::replace(&mut self.last_use_time, Arc::new(Instant::now())); message } @@ -276,18 +282,20 @@ impl Session { /// that encrypted this Olm message. /// /// * `message` - The pre-key Olm message that should be checked. - pub fn matches( + 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) -> String { - self.inner.session_id() + pub fn session_id(&self) -> &str { + &self.session_id } /// Store the session as a base64 encoded string. @@ -296,8 +304,8 @@ impl Session { /// /// * `pickle_mode` - The mode that was used to pickle the session, either /// an unencrypted mode or an encrypted using passphrase. - pub fn pickle(&self, pickle_mode: PicklingMode) -> String { - self.inner.pickle(pickle_mode) + pub async fn pickle(&self, pickle_mode: PicklingMode) -> String { + self.inner.lock().await.pickle(pickle_mode) } /// Restore a Session from a previously pickled string. @@ -328,11 +336,14 @@ impl Session { last_use_time: Instant, ) -> Result { let session = OlmSession::unpickle(pickle, pickle_mode)?; + let session_id = session.session_id(); + Ok(Session { - inner: session, - sender_key, - creation_time, - last_use_time, + 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), }) } } @@ -665,7 +676,7 @@ mod test { let plaintext = "Hello world"; - let message = bob_session.encrypt(plaintext); + let message = bob_session.encrypt(plaintext).await; let prekey_message = match message.clone() { OlmMessage::PreKey(m) => m, @@ -680,7 +691,7 @@ mod test { assert_eq!(bob_session.session_id(), alice_session.session_id()); - let decyrpted = alice_session.decrypt(message).unwrap(); + let decyrpted = alice_session.decrypt(message).await.unwrap(); assert_eq!(plaintext, decyrpted); } } diff --git a/src/crypto/store/memorystore.rs b/src/crypto/store/memorystore.rs index d14e29b7..6daa1dba 100644 --- a/src/crypto/store/memorystore.rs +++ b/src/crypto/store/memorystore.rs @@ -52,7 +52,7 @@ impl CryptoStore for MemoryStore { Ok(()) } - async fn save_session(&mut self, _: Arc>) -> Result<()> { + async fn save_session(&mut self, _: Session) -> Result<()> { Ok(()) } @@ -61,10 +61,7 @@ impl CryptoStore for MemoryStore { Ok(()) } - async fn get_sessions( - &mut self, - sender_key: &str, - ) -> Result>>>>>> { + async fn get_sessions(&mut self, sender_key: &str) -> Result>>>> { Ok(self.sessions.get(sender_key)) } diff --git a/src/crypto/store/mod.rs b/src/crypto/store/mod.rs index 016cb334..d059b3a7 100644 --- a/src/crypto/store/mod.rs +++ b/src/crypto/store/mod.rs @@ -68,12 +68,9 @@ pub trait CryptoStore: Debug + Send + Sync { async fn load_account(&mut self) -> Result>; async fn save_account(&mut self, account: Account) -> Result<()>; - async fn save_session(&mut self, session: Arc>) -> Result<()>; + async fn save_session(&mut self, session: Session) -> Result<()>; async fn add_and_save_session(&mut self, session: Session) -> Result<()>; - async fn get_sessions( - &mut self, - sender_key: &str, - ) -> Result>>>>>>; + async fn get_sessions(&mut self, sender_key: &str) -> Result>>>>; async fn save_inbound_group_session(&mut self, session: InboundGroupSession) -> Result; async fn get_inbound_group_session( diff --git a/src/crypto/store/sqlite.rs b/src/crypto/store/sqlite.rs index 1b57a832..cc4aea7c 100644 --- a/src/crypto/store/sqlite.rs +++ b/src/crypto/store/sqlite.rs @@ -155,7 +155,7 @@ impl SqliteStore { async fn get_sessions_for( &mut self, sender_key: &str, - ) -> Result>>>>>> { + ) -> Result>>>> { let loaded_sessions = self.sessions.get(sender_key).is_some(); if !loaded_sessions { @@ -169,7 +169,7 @@ impl SqliteStore { Ok(self.sessions.get(sender_key)) } - async fn load_sessions_for(&mut self, sender_key: &str) -> Result>>> { + async fn load_sessions_for(&mut self, sender_key: &str) -> Result> { let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; let mut connection = self.connection.lock().await; @@ -196,15 +196,15 @@ impl SqliteStore { .checked_sub(serde_json::from_str::(&row.3)?) .ok_or(CryptoStoreError::SessionTimestampError)?; - Ok(Arc::new(Mutex::new(Session::from_pickle( + Ok(Session::from_pickle( pickle.to_string(), self.get_pickle_mode(), sender_key.to_string(), creation_time, last_use_time, - )?))) + )?) }) - .collect::>>>>()?) + .collect::>>()?) } async fn load_inbound_group_sessions(&self) -> Result> { @@ -322,15 +322,13 @@ impl CryptoStore for SqliteStore { Ok(()) } - async fn save_session(&mut self, session: Arc>) -> Result<()> { + async fn save_session(&mut self, session: Session) -> Result<()> { let account_id = self.account_id.ok_or(CryptoStoreError::AccountUnset)?; - let session = session.lock().await; - 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())?; - let pickle = session.pickle(self.get_pickle_mode()); + let pickle = session.pickle(self.get_pickle_mode()).await; let mut connection = self.connection.lock().await; @@ -341,9 +339,9 @@ impl CryptoStore for SqliteStore { ) .bind(&session_id) .bind(&account_id) - .bind(&creation_time) - .bind(&last_use_time) - .bind(&session.sender_key) + .bind(&*creation_time) + .bind(&*last_use_time) + .bind(&*session.sender_key) .bind(&pickle) .execute(&mut *connection) .await?; @@ -357,10 +355,7 @@ impl CryptoStore for SqliteStore { Ok(()) } - async fn get_sessions( - &mut self, - sender_key: &str, - ) -> Result>>>>>> { + async fn get_sessions(&mut self, sender_key: &str) -> Result>>>> { Ok(self.get_sessions_for(sender_key).await?) } @@ -565,7 +560,6 @@ mod test { async fn save_session() { let mut store = get_store().await; let (account, session) = get_account_and_session().await; - let session = Arc::new(Mutex::new(session)); assert!(store.save_session(session.clone()).await.is_err()); @@ -581,22 +575,19 @@ mod test { async fn load_sessions() { let mut store = get_store().await; let (account, session) = get_account_and_session().await; - let session = Arc::new(Mutex::new(session)); store .save_account(account.clone()) .await .expect("Can't save account"); store.save_session(session.clone()).await.unwrap(); - let sess = session.lock().await; - let sessions = store - .load_sessions_for(&sess.sender_key) + .load_sessions_for(&session.sender_key) .await .expect("Can't load sessions"); let loaded_session = &sessions[0]; - assert_eq!(*sess, *loaded_session.lock().await); + assert_eq!(&session, loaded_session); } #[tokio::test] @@ -604,7 +595,7 @@ mod test { let mut store = get_store().await; let (account, session) = get_account_and_session().await; let sender_key = session.sender_key.to_owned(); - let session_id = session.session_id(); + let session_id = session.session_id().to_owned(); store .save_account(account.clone()) @@ -616,7 +607,7 @@ mod test { let sessions_lock = sessions.lock().await; let session = &sessions_lock[0]; - assert_eq!(session_id, *session.lock().await.session_id()); + assert_eq!(session_id, session.session_id()); } #[tokio::test] From 5f2269f12fa82cbfca8b1d157aba996c9eb868dd Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 08:39:51 -0400 Subject: [PATCH 12/29] party bot example responds to !party --- examples/command_bot.rs | 129 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 examples/command_bot.rs diff --git a/examples/command_bot.rs b/examples/command_bot.rs new file mode 100644 index 00000000..d64c911f --- /dev/null +++ b/examples/command_bot.rs @@ -0,0 +1,129 @@ +use std::ops::Deref; +use std::sync::Arc; +use std::{env, process::exit}; +use url::Url; + +use matrix_sdk::{ + self, + events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + identifiers::RoomId, + AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, +}; +use tokio::runtime::Handle; +use tokio::sync::{ + mpsc::{self, Receiver, Sender}, + Mutex, +}; + +struct CommandBot { + send: Sender<(RoomId, String)>, +} + +impl CommandBot { + pub fn new(send: Sender<(RoomId, String)>) -> Self { + Self { send } + } +} + +#[async_trait::async_trait] +impl EventEmitter for CommandBot { + async fn on_room_message(&mut self, room: Arc>, event: Arc>) { + if let MessageEvent { + content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), + sender, + .. + } = event.lock().await.deref() + { + let room = room.lock().await; + if msg_body.contains("!party") { + self.send + .send((room.room_id.clone(), "let's PARTY!! 🥳🎊🎉".to_string())) + .await + .unwrap() + } + } + } +} + +#[allow(clippy::for_loop_over_option)] +async fn login_and_sync( + homeserver_url: String, + username: String, + password: String, + exec: Handle, +) -> Result<(), matrix_sdk::Error> { + let client_config = AsyncClientConfig::new(); + // .proxy("http://localhost:8080")? + // .disable_ssl_verification(); + let homeserver_url = Url::parse(&homeserver_url)?; + let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); + + let (send, mut recv) = mpsc::channel(100); + + client + .add_event_emitter(Arc::new(Mutex::new(Box::new(CommandBot::new(send))))) + .await; + + client + .login( + username.clone(), + password, + None, + Some("command bot".to_string()), + ) + .await?; + + println!("logged in as user {}", username); + + let client = Arc::new(Mutex::new(client)); + let send_client = Arc::clone(&client); + + exec.spawn(async move { + for (id, msg) in recv.recv().await { + let content = MessageEventContent::Text(TextMessageEventContent { + body: msg, + format: None, + formatted_body: None, + relates_to: None, + }); + send_client + .lock() + .await + .room_send(&id, content) + .await + .unwrap(); + } + }); + + client + .lock() + .await + .sync_forever(SyncSettings::new(), |_| async {}) + .await; + + Ok(()) +} + +fn main() -> Result<(), matrix_sdk::Error> { + let (homeserver_url, username, password) = + match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { + (Some(a), Some(b), Some(c)) => (a, b, c), + _ => { + eprintln!( + "Usage: {} ", + env::args().next().unwrap() + ); + exit(1) + } + }; + + let mut runtime = tokio::runtime::Builder::new() + .basic_scheduler() + .threaded_scheduler() + .enable_all() + .build() + .unwrap(); + + let executor = runtime.handle().clone(); + runtime.block_on(async { login_and_sync(homeserver_url, username, password, executor).await }) +} From 6f2b5194d13ebf29a691f1e798388d4b445a0898 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 08:45:40 -0400 Subject: [PATCH 13/29] remove unused imports/vars --- examples/command_bot.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index d64c911f..333ab28e 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -11,7 +11,7 @@ use matrix_sdk::{ }; use tokio::runtime::Handle; use tokio::sync::{ - mpsc::{self, Receiver, Sender}, + mpsc::{self, Sender}, Mutex, }; @@ -30,14 +30,16 @@ impl EventEmitter for CommandBot { async fn on_room_message(&mut self, room: Arc>, event: Arc>) { if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), - sender, .. } = event.lock().await.deref() { let room = room.lock().await; if msg_body.contains("!party") { self.send - .send((room.room_id.clone(), "let's PARTY!! 🥳🎊🎉".to_string())) + .send(( + room.room_id.clone(), + "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), + )) .await .unwrap() } @@ -89,7 +91,7 @@ async fn login_and_sync( send_client .lock() .await - .room_send(&id, content) + .room_send(&id, content, None) .await .unwrap(); } From 87c9dbdad73b493866c9e036df737e89f3b0636f Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 14:49:29 -0400 Subject: [PATCH 14/29] clone client --- examples/command_bot.rs | 80 +++++++++++++---------------------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 333ab28e..fdfada22 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -6,22 +6,17 @@ use url::Url; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - identifiers::RoomId, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; -use tokio::runtime::Handle; -use tokio::sync::{ - mpsc::{self, Sender}, - Mutex, -}; +use tokio::sync::Mutex; struct CommandBot { - send: Sender<(RoomId, String)>, + client: AsyncClient, } impl CommandBot { - pub fn new(send: Sender<(RoomId, String)>) -> Self { - Self { send } + pub fn new(client: AsyncClient) -> Self { + Self { client } } } @@ -35,13 +30,18 @@ impl EventEmitter for CommandBot { { let room = room.lock().await; if msg_body.contains("!party") { - self.send - .send(( - room.room_id.clone(), - "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), - )) + println!("!party found"); + let content = MessageEventContent::Text(TextMessageEventContent { + body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), + format: None, + formatted_body: None, + relates_to: None, + }); + self.client + .room_send(&room.room_id, content, None) .await - .unwrap() + .unwrap(); + println!("message sent"); } } } @@ -52,7 +52,6 @@ async fn login_and_sync( homeserver_url: String, username: String, password: String, - exec: Handle, ) -> Result<(), matrix_sdk::Error> { let client_config = AsyncClientConfig::new(); // .proxy("http://localhost:8080")? @@ -60,10 +59,10 @@ async fn login_and_sync( let homeserver_url = Url::parse(&homeserver_url)?; let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); - let (send, mut recv) = mpsc::channel(100); - client - .add_event_emitter(Arc::new(Mutex::new(Box::new(CommandBot::new(send))))) + .add_event_emitter(Arc::new(Mutex::new(Box::new(CommandBot::new( + client.clone(), + ))))) .await; client @@ -75,38 +74,15 @@ async fn login_and_sync( ) .await?; - println!("logged in as user {}", username); + println!("logged in as {}", username); - let client = Arc::new(Mutex::new(client)); - let send_client = Arc::clone(&client); - - exec.spawn(async move { - for (id, msg) in recv.recv().await { - let content = MessageEventContent::Text(TextMessageEventContent { - body: msg, - format: None, - formatted_body: None, - relates_to: None, - }); - send_client - .lock() - .await - .room_send(&id, content, None) - .await - .unwrap(); - } - }); - - client - .lock() - .await - .sync_forever(SyncSettings::new(), |_| async {}) - .await; + client.sync(SyncSettings::new()).await.unwrap(); Ok(()) } -fn main() -> Result<(), matrix_sdk::Error> { +#[tokio::main] +async fn main() -> Result<(), matrix_sdk::Error> { let (homeserver_url, username, password) = match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { (Some(a), Some(b), Some(c)) => (a, b, c), @@ -118,14 +94,6 @@ fn main() -> Result<(), matrix_sdk::Error> { exit(1) } }; - - let mut runtime = tokio::runtime::Builder::new() - .basic_scheduler() - .threaded_scheduler() - .enable_all() - .build() - .unwrap(); - - let executor = runtime.handle().clone(); - runtime.block_on(async { login_and_sync(homeserver_url, username, password, executor).await }) + login_and_sync(homeserver_url, username, password).await?; + Ok(()) } From a5ab7d97dac03ff95f2d113e348e31e900034e33 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 15:16:20 -0400 Subject: [PATCH 15/29] use AsyncClient::sync_forever --- examples/command_bot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index fdfada22..aaaa6b81 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -76,7 +76,7 @@ async fn login_and_sync( println!("logged in as {}", username); - client.sync(SyncSettings::new()).await.unwrap(); + client.sync_forever(SyncSettings::new(), |_| async {}).await; Ok(()) } From cf029b2e4f998829f50e8178cb20fad3bfda4d93 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 18:10:10 -0400 Subject: [PATCH 16/29] make EventEmitter shared refs, in AsyncClient::sync lock only in inner most scope --- examples/command_bot.rs | 80 +++++++++----- examples/login.rs | 11 +- src/async_client.rs | 22 ++-- src/base_client.rs | 218 +++++++++------------------------------ src/event_emitter/mod.rs | 171 +++++++++--------------------- src/models/room.rs | 2 +- 6 files changed, 165 insertions(+), 339 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index aaaa6b81..222b4e34 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -8,41 +8,55 @@ use matrix_sdk::{ events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; +use tokio::runtime::{Handle, Runtime}; +use tokio::sync::mpsc::{channel, Sender}; use tokio::sync::Mutex; struct CommandBot { - client: AsyncClient, + client: Mutex, + // sender: Sender<(RoomId, MessageEventContent)> } impl CommandBot { pub fn new(client: AsyncClient) -> Self { - Self { client } + Self { + client: Mutex::new(client), + } } } #[async_trait::async_trait] impl EventEmitter for CommandBot { - async fn on_room_message(&mut self, room: Arc>, event: Arc>) { - if let MessageEvent { + async fn on_room_message(&self, room: &Room, event: &MessageEvent) { + let msg_body = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. - } = event.lock().await.deref() + } = event { - let room = room.lock().await; - if msg_body.contains("!party") { - println!("!party found"); - let content = MessageEventContent::Text(TextMessageEventContent { - body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), - format: None, - formatted_body: None, - relates_to: None, - }); - self.client - .room_send(&room.room_id, content, None) - .await - .unwrap(); - println!("message sent"); - } + msg_body.clone() + } else { + String::new() + }; + + if msg_body.contains("!party") { + let content = MessageEventContent::Text(TextMessageEventContent { + body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), + format: None, + formatted_body: None, + relates_to: None, + }); + let room_id = &room.room_id; + + println!("sending"); + + self.client + .lock() + .await + .room_send(&room_id, content, None) + .await + .unwrap(); + + println!("message sent"); } } } @@ -52,6 +66,7 @@ async fn login_and_sync( homeserver_url: String, username: String, password: String, + exec: Handle, ) -> Result<(), matrix_sdk::Error> { let client_config = AsyncClientConfig::new(); // .proxy("http://localhost:8080")? @@ -60,9 +75,7 @@ async fn login_and_sync( let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); client - .add_event_emitter(Arc::new(Mutex::new(Box::new(CommandBot::new( - client.clone(), - ))))) + .add_event_emitter(Box::new(CommandBot::new(client.clone()))) .await; client @@ -76,13 +89,16 @@ async fn login_and_sync( println!("logged in as {}", username); - client.sync_forever(SyncSettings::new(), |_| async {}).await; + exec.spawn(async move { + client.sync_forever(SyncSettings::new(), |_| async {}).await; + }) + .await + .unwrap(); Ok(()) } -#[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +fn main() -> Result<(), matrix_sdk::Error> { let (homeserver_url, username, password) = match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { (Some(a), Some(b), Some(c)) => (a, b, c), @@ -94,6 +110,16 @@ async fn main() -> Result<(), matrix_sdk::Error> { exit(1) } }; - login_and_sync(homeserver_url, username, password).await?; + + let mut runtime = tokio::runtime::Builder::new() + .basic_scheduler() + .threaded_scheduler() + .enable_all() + .build() + .unwrap(); + + let exec = runtime.handle().clone(); + + runtime.block_on(async { login_and_sync(homeserver_url, username, password, exec).await })?; Ok(()) } diff --git a/examples/login.rs b/examples/login.rs index e69a9087..34f943a2 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -14,15 +14,14 @@ struct EventCallback; #[async_trait::async_trait] impl EventEmitter for EventCallback { - async fn on_room_message(&mut self, room: Arc>, event: Arc>) { + async fn on_room_message(&self, room: &Room, event: &MessageEvent) { if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, .. - } = event.lock().await.deref() + } = event { - let rooms = room.lock().await; - let member = rooms.members.get(&sender).unwrap(); + let member = room.members.get(&sender).unwrap(); println!( "{}: {}", member.display_name.as_ref().unwrap_or(&sender.to_string()), @@ -43,9 +42,7 @@ async fn login( let homeserver_url = Url::parse(&homeserver_url)?; let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); - client - .add_event_emitter(Arc::new(Mutex::new(Box::new(EventCallback)))) - .await; + client.add_event_emitter(Box::new(EventCallback)).await; client .login(username, password, None, Some("rust-sdk".to_string())) diff --git a/src/async_client.rs b/src/async_client.rs index 718e423b..d52b724c 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -273,10 +273,7 @@ impl AsyncClient { /// Add `EventEmitter` to `AsyncClient`. /// /// The methods of `EventEmitter` are called when the respective `RoomEvents` occur. - pub async fn add_event_emitter( - &mut self, - emitter: Arc>>, - ) { + pub async fn add_event_emitter(&mut self, emitter: Box) { self.base_client.write().await.event_emitter = Some(emitter); } @@ -301,7 +298,7 @@ impl AsyncClient { /// Returns the rooms this client knows about. /// /// A `HashMap` of room id to `matrix::models::Room` - pub async fn get_rooms(&self) -> HashMap>> { + pub async fn get_rooms(&self) -> HashMap>> { self.base_client.read().await.joined_rooms.clone() } @@ -565,9 +562,8 @@ impl AsyncClient { let mut response = self.send(request).await?; for (room_id, room) in &mut response.rooms.join { - let mut client = self.base_client.write().await; - let _matrix_room = { + let mut client = self.base_client.write().await; for event in &room.state.events { if let EventResult::Ok(e) = event { client.receive_joined_state_event(&room_id, &e).await; @@ -580,12 +576,14 @@ impl AsyncClient { // re looping is not ideal here for event in &mut room.state.events { if let EventResult::Ok(e) = event { + let client = self.base_client.read().await; client.emit_state_event(room_id, e).await; } } for mut event in &mut room.timeline.events { let decrypted_event = { + let mut client = self.base_client.write().await; client .receive_joined_timeline_event(room_id, &mut event) .await @@ -596,6 +594,7 @@ impl AsyncClient { } if let EventResult::Ok(e) = event { + let client = self.base_client.read().await; client.emit_timeline_event(room_id, e).await; } } @@ -604,8 +603,8 @@ impl AsyncClient { for account_data in &mut room.account_data.events { { if let EventResult::Ok(e) = account_data { + let mut client = self.base_client.write().await; client.receive_account_data_event(&room_id, e).await; - client.emit_account_data_event(room_id, e).await; } } @@ -617,6 +616,7 @@ impl AsyncClient { for presence in &mut response.presence.events { { if let EventResult::Ok(e) = presence { + let mut client = self.base_client.write().await; client.receive_presence_event(&room_id, e).await; client.emit_presence_event(room_id, e).await; @@ -627,6 +627,7 @@ impl AsyncClient { for ephemeral in &mut room.ephemeral.events { { if let EventResult::Ok(e) = ephemeral { + let mut client = self.base_client.write().await; client.receive_ephemeral_event(&room_id, e).await; client.emit_ephemeral_event(room_id, e).await; @@ -810,7 +811,6 @@ impl AsyncClient { } else { request_builder }; - let mut response = request_builder.send().await?; trace!("Got response: {:?}", response); @@ -892,7 +892,7 @@ impl AsyncClient { let room = client.joined_rooms.get(room_id); match room { - Some(r) => r.lock().await.is_encrypted(), + Some(r) => r.write().await.is_encrypted(), None => false, } }; @@ -901,7 +901,7 @@ impl AsyncClient { let missing_sessions = { let client = self.base_client.read().await; let room = client.joined_rooms.get(room_id); - let room = room.as_ref().unwrap().lock().await; + let room = room.as_ref().unwrap().write().await; let users = room.members.keys(); self.base_client .read() diff --git a/src/base_client.rs b/src/base_client.rs index 12164686..5c8a5bf3 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; #[cfg(feature = "encryption")] use std::collections::HashSet; use std::fmt; +use std::ops::Deref; use std::sync::Arc; #[cfg(feature = "encryption")] @@ -36,7 +37,9 @@ use crate::models::Room; use crate::session::Session; use crate::EventEmitter; +#[cfg(feature = "encryption")] use tokio::sync::Mutex; +use tokio::sync::RwLock; #[cfg(feature = "encryption")] use crate::crypto::{OlmMachine, OneTimeKeys}; @@ -65,14 +68,14 @@ pub struct Client { /// The current sync token that should be used for the next sync call. pub sync_token: Option, /// A map of the rooms our user is joined in. - pub joined_rooms: HashMap>>, + pub joined_rooms: HashMap>>, /// A list of ignored users. pub ignored_users: Vec, /// The push ruleset for the logged in user. pub push_ruleset: Option, /// Any implementor of EventEmitter will act as the callbacks for various /// events. - pub event_emitter: Option>>>, + pub event_emitter: Option>, #[cfg(feature = "encryption")] olm: Arc>>, @@ -125,10 +128,7 @@ impl Client { /// Add `EventEmitter` to `Client`. /// /// The methods of `EventEmitter` are called when the respective `RoomEvents` occur. - pub async fn add_event_emitter( - &mut self, - emitter: Arc>>, - ) { + pub async fn add_event_emitter(&mut self, emitter: Box) { self.event_emitter = Some(emitter); } @@ -160,7 +160,7 @@ impl Client { pub(crate) async fn calculate_room_name(&self, room_id: &RoomId) -> Option { if let Some(room) = self.joined_rooms.get(room_id) { - let room = room.lock().await; + let room = room.read().await; Some(room.room_name.calculate_name(room_id, &room.members)) } else { None @@ -170,17 +170,17 @@ impl Client { pub(crate) async fn calculate_room_names(&self) -> Vec { let mut res = Vec::new(); for (id, room) in &self.joined_rooms { - let room = room.lock().await; + let room = room.read().await; res.push(room.room_name.calculate_name(id, &room.members)) } res } - pub(crate) fn get_or_create_room(&mut self, room_id: &RoomId) -> &mut Arc> { + pub(crate) fn get_or_create_room(&mut self, room_id: &RoomId) -> &mut Arc> { #[allow(clippy::or_fun_call)] self.joined_rooms .entry(room_id.clone()) - .or_insert(Arc::new(Mutex::new(Room::new( + .or_insert(Arc::new(RwLock::new(Room::new( room_id, &self .session @@ -190,7 +190,7 @@ impl Client { )))) } - pub(crate) fn get_room(&self, room_id: &RoomId) -> Option<&Arc>> { + pub(crate) fn get_room(&self, room_id: &RoomId) -> Option<&Arc>> { self.joined_rooms.get(room_id) } @@ -259,7 +259,7 @@ impl Client { } } - let mut room = self.get_or_create_room(&room_id).lock().await; + let mut room = self.get_or_create_room(&room_id).write().await; room.receive_timeline_event(e); decrypted_event } @@ -282,7 +282,7 @@ impl Client { room_id: &RoomId, event: &StateEvent, ) -> bool { - let mut room = self.get_or_create_room(room_id).lock().await; + let mut room = self.get_or_create_room(room_id).write().await; room.receive_state_event(event) } @@ -303,7 +303,7 @@ impl Client { ) -> bool { // this should be the room that was just created in the `Client::sync` loop. if let Some(room) = self.get_room(room_id) { - let mut room = room.lock().await; + let mut room = room.write().await; room.receive_presence_event(event) } else { false @@ -376,7 +376,7 @@ impl Client { // part where we already iterate through the rooms to avoid yet // another room loop. for room in self.joined_rooms.values() { - let room = room.lock().await; + let room = room.write().await; if !room.is_encrypted() { continue; } @@ -459,7 +459,7 @@ impl Client { match &mut *olm { Some(o) => { - let room = room.lock().await; + let room = room.write().await; let members = room.members.keys(); Ok(o.share_group_session(room_id, members).await?) } @@ -573,37 +573,26 @@ impl Client { Ok(()) } - pub(crate) async fn emit_timeline_event(&mut self, room_id: &RoomId, event: &mut RoomEvent) { + pub(crate) async fn emit_timeline_event(&self, room_id: &RoomId, event: &RoomEvent) { match event { RoomEvent::RoomMember(mem) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_member(Arc::clone(&room), Arc::new(Mutex::new(mem.clone()))) - .await; + ee.on_room_member(room.read().await.deref(), &mem).await; } } } RoomEvent::RoomName(name) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_name(Arc::clone(&room), Arc::new(Mutex::new(name.clone()))) - .await; + ee.on_room_name(room.read().await.deref(), &name).await; } } } RoomEvent::RoomCanonicalAlias(canonical) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_canonical_alias( - Arc::clone(&room), - Arc::new(Mutex::new(canonical.clone())), - ) + ee.on_room_canonical_alias(room.read().await.deref(), &canonical) .await; } } @@ -611,12 +600,7 @@ impl Client { RoomEvent::RoomAliases(aliases) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_aliases( - Arc::clone(&room), - Arc::new(Mutex::new(aliases.clone())), - ) + ee.on_room_aliases(room.read().await.deref(), &aliases) .await; } } @@ -624,32 +608,21 @@ impl Client { RoomEvent::RoomAvatar(avatar) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_avatar(Arc::clone(&room), Arc::new(Mutex::new(avatar.clone()))) - .await; + ee.on_room_avatar(room.read().await.deref(), &avatar).await; } } } RoomEvent::RoomMessage(msg) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_message(Arc::clone(&room), Arc::new(Mutex::new(msg.clone()))) - .await; + ee.on_room_message(room.read().await.deref(), &msg).await; } } } RoomEvent::RoomMessageFeedback(msg_feedback) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_message_feedback( - Arc::clone(&room), - Arc::new(Mutex::new(msg_feedback.clone())), - ) + ee.on_room_message_feedback(room.read().await.deref(), &msg_feedback) .await; } } @@ -657,12 +630,7 @@ impl Client { RoomEvent::RoomRedaction(redaction) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_redaction( - Arc::clone(&room), - Arc::new(Mutex::new(redaction.clone())), - ) + ee.on_room_redaction(room.read().await.deref(), &redaction) .await; } } @@ -670,12 +638,7 @@ impl Client { RoomEvent::RoomPowerLevels(power) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_room_power_levels( - Arc::clone(&room), - Arc::new(Mutex::new(power.clone())), - ) + ee.on_room_power_levels(room.read().await.deref(), &power) .await; } } @@ -684,40 +647,26 @@ impl Client { } } - pub(crate) async fn emit_state_event(&mut self, room_id: &RoomId, event: &mut StateEvent) { + pub(crate) async fn emit_state_event(&self, room_id: &RoomId, event: &StateEvent) { match event { StateEvent::RoomMember(member) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_member( - Arc::clone(&room), - Arc::new(Mutex::new(member.clone())), - ) - .await; + ee.on_state_member(room.read().await.deref(), &member).await; } } } StateEvent::RoomName(name) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_name(Arc::clone(&room), Arc::new(Mutex::new(name.clone()))) - .await; + ee.on_state_name(room.read().await.deref(), &name).await; } } } StateEvent::RoomCanonicalAlias(canonical) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_canonical_alias( - Arc::clone(&room), - Arc::new(Mutex::new(canonical.clone())), - ) + ee.on_state_canonical_alias(room.read().await.deref(), &canonical) .await; } } @@ -725,12 +674,7 @@ impl Client { StateEvent::RoomAliases(aliases) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_aliases( - Arc::clone(&room), - Arc::new(Mutex::new(aliases.clone())), - ) + ee.on_state_aliases(room.read().await.deref(), &aliases) .await; } } @@ -738,25 +682,14 @@ impl Client { StateEvent::RoomAvatar(avatar) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_avatar( - Arc::clone(&room), - Arc::new(Mutex::new(avatar.clone())), - ) - .await; + ee.on_state_avatar(room.read().await.deref(), &avatar).await; } } } StateEvent::RoomPowerLevels(power) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_power_levels( - Arc::clone(&room), - Arc::new(Mutex::new(power.clone())), - ) + ee.on_state_power_levels(room.read().await.deref(), &power) .await; } } @@ -764,12 +697,7 @@ impl Client { StateEvent::RoomJoinRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_state_join_rules( - Arc::clone(&room), - Arc::new(Mutex::new(rules.clone())), - ) + ee.on_state_join_rules(room.read().await.deref(), &rules) .await; } } @@ -778,21 +706,12 @@ impl Client { } } - pub(crate) async fn emit_account_data_event( - &mut self, - room_id: &RoomId, - event: &mut NonRoomEvent, - ) { + pub(crate) async fn emit_account_data_event(&self, room_id: &RoomId, event: &NonRoomEvent) { match event { NonRoomEvent::Presence(presence) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_presence( - Arc::clone(&room), - Arc::new(Mutex::new(presence.clone())), - ) + ee.on_account_presence(room.read().await.deref(), &presence) .await; } } @@ -800,12 +719,7 @@ impl Client { NonRoomEvent::IgnoredUserList(ignored) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_ignored_users( - Arc::clone(&room), - Arc::new(Mutex::new(ignored.clone())), - ) + ee.on_account_ignored_users(room.read().await.deref(), &ignored) .await; } } @@ -813,12 +727,7 @@ impl Client { NonRoomEvent::PushRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_push_rules( - Arc::clone(&room), - Arc::new(Mutex::new(rules.clone())), - ) + ee.on_account_push_rules(room.read().await.deref(), &rules) .await; } } @@ -826,12 +735,7 @@ impl Client { NonRoomEvent::FullyRead(full_read) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_data_fully_read( - Arc::clone(&room), - Arc::new(Mutex::new(full_read.clone())), - ) + ee.on_account_data_fully_read(room.read().await.deref(), &full_read) .await; } } @@ -840,21 +744,12 @@ impl Client { } } - pub(crate) async fn emit_ephemeral_event( - &mut self, - room_id: &RoomId, - event: &mut NonRoomEvent, - ) { + pub(crate) async fn emit_ephemeral_event(&self, room_id: &RoomId, event: &NonRoomEvent) { match event { NonRoomEvent::Presence(presence) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_presence( - Arc::clone(&room), - Arc::new(Mutex::new(presence.clone())), - ) + ee.on_account_presence(room.read().await.deref(), &presence) .await; } } @@ -862,12 +757,7 @@ impl Client { NonRoomEvent::IgnoredUserList(ignored) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_ignored_users( - Arc::clone(&room), - Arc::new(Mutex::new(ignored.clone())), - ) + ee.on_account_ignored_users(room.read().await.deref(), &ignored) .await; } } @@ -875,12 +765,7 @@ impl Client { NonRoomEvent::PushRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_push_rules( - Arc::clone(&room), - Arc::new(Mutex::new(rules.clone())), - ) + ee.on_account_push_rules(room.read().await.deref(), &rules) .await; } } @@ -888,12 +773,7 @@ impl Client { NonRoomEvent::FullyRead(full_read) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_account_data_fully_read( - Arc::clone(&room), - Arc::new(Mutex::new(full_read.clone())), - ) + ee.on_account_data_fully_read(room.read().await.deref(), &full_read) .await; } } @@ -902,16 +782,10 @@ impl Client { } } - pub(crate) async fn emit_presence_event( - &mut self, - room_id: &RoomId, - event: &mut PresenceEvent, - ) { + pub(crate) async fn emit_presence_event(&self, room_id: &RoomId, event: &PresenceEvent) { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.lock() - .await - .on_presence_event(Arc::clone(&room), Arc::new(Mutex::new(event.clone()))) + ee.on_presence_event(room.read().await.deref(), &event) .await; } } diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index 0129cadf..6ee62d18 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -13,8 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; - use crate::events::{ fully_read::FullyReadEvent, ignored_user_list::IgnoredUserListEvent, @@ -34,7 +32,6 @@ use crate::events::{ }; use crate::models::Room; -use tokio::sync::Mutex; /// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event. /// The `AsyncClient` calls each method when the corresponding event is received. /// @@ -57,15 +54,14 @@ use tokio::sync::Mutex; /// /// #[async_trait::async_trait] /// impl EventEmitter for EventCallback { -/// async fn on_room_message(&mut self, room: Arc>, event: Arc>) { +/// async fn on_room_message(&self, room: &Room, event: &MessageEvent) { /// if let MessageEvent { /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// sender, /// .. -/// } = event.lock().await.deref() +/// } = event /// { -/// let rooms = room.lock().await; -/// let member = rooms.members.get(&sender).unwrap(); +/// let member = room.members.get(&sender).unwrap(); /// println!( /// "{}: {}", /// member @@ -82,193 +78,128 @@ use tokio::sync::Mutex; pub trait EventEmitter: Send + Sync { // ROOM EVENTS from `IncomingTimeline` /// Fires when `AsyncClient` receives a `RoomEvent::RoomMember` event. - async fn on_room_member(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_member(&self, _: &Room, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomName` event. - async fn on_room_name(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_name(&self, _: &Room, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomCanonicalAlias` event. - async fn on_room_canonical_alias( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_room_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAliases` event. - async fn on_room_aliases(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_aliases(&self, _: &Room, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAvatar` event. - async fn on_room_avatar(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_avatar(&self, _: &Room, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessage` event. - async fn on_room_message(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_message(&self, _: &Room, _: &MessageEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessageFeedback` event. - async fn on_room_message_feedback( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_room_message_feedback(&self, _: &Room, _: &FeedbackEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomRedaction` event. - async fn on_room_redaction(&mut self, _: Arc>, _: Arc>) {} + async fn on_room_redaction(&self, _: &Room, _: &RedactionEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomPowerLevels` event. - async fn on_room_power_levels(&mut self, _: Arc>, _: Arc>) { - } + async fn on_room_power_levels(&self, _: &Room, _: &PowerLevelsEvent) {} // `RoomEvent`s from `IncomingState` /// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event. - async fn on_state_member(&mut self, _: Arc>, _: Arc>) {} + async fn on_state_member(&self, _: &Room, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomName` event. - async fn on_state_name(&mut self, _: Arc>, _: Arc>) {} + async fn on_state_name(&self, _: &Room, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomCanonicalAlias` event. - async fn on_state_canonical_alias( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_state_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAliases` event. - async fn on_state_aliases(&mut self, _: Arc>, _: Arc>) {} + async fn on_state_aliases(&self, _: &Room, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAvatar` event. - async fn on_state_avatar(&mut self, _: Arc>, _: Arc>) {} + async fn on_state_avatar(&self, _: &Room, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomPowerLevels` event. - async fn on_state_power_levels( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_state_power_levels(&self, _: &Room, _: &PowerLevelsEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomJoinRules` event. - async fn on_state_join_rules(&mut self, _: Arc>, _: Arc>) {} + async fn on_state_join_rules(&self, _: &Room, _: &JoinRulesEvent) {} // `NonRoomEvent` (this is a type alias from ruma_events) from `IncomingAccountData` /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomMember` event. - async fn on_account_presence(&mut self, _: Arc>, _: Arc>) {} + async fn on_account_presence(&self, _: &Room, _: &PresenceEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomName` event. - async fn on_account_ignored_users( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_account_ignored_users(&self, _: &Room, _: &IgnoredUserListEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomCanonicalAlias` event. - async fn on_account_push_rules(&mut self, _: Arc>, _: Arc>) {} + async fn on_account_push_rules(&self, _: &Room, _: &PushRulesEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_account_data_fully_read( - &mut self, - _: Arc>, - _: Arc>, - ) { - } + async fn on_account_data_fully_read(&self, _: &Room, _: &FullyReadEvent) {} // `PresenceEvent` is a struct so there is only the one method /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_presence_event(&mut self, _: Arc>, _: Arc>) {} + async fn on_presence_event(&self, _: &Room, _: &PresenceEvent) {} } #[cfg(test)] mod test { use super::*; - + use std::sync::Arc; + use tokio::sync::Mutex; + #[derive(Clone)] pub struct EvEmitterTest(Arc>>); #[async_trait::async_trait] impl EventEmitter for EvEmitterTest { - async fn on_room_member(&mut self, _: Arc>, _: Arc>) { + async fn on_room_member(&self, _: &Room, _: &MemberEvent) { self.0.lock().await.push("member".to_string()) } - async fn on_room_name(&mut self, _: Arc>, _: Arc>) { + async fn on_room_name(&self, _: &Room, _: &NameEvent) { self.0.lock().await.push("name".to_string()) } - async fn on_room_canonical_alias( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_room_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) { self.0.lock().await.push("canonical".to_string()) } - async fn on_room_aliases(&mut self, _: Arc>, _: Arc>) { + async fn on_room_aliases(&self, _: &Room, _: &AliasesEvent) { self.0.lock().await.push("aliases".to_string()) } - async fn on_room_avatar(&mut self, _: Arc>, _: Arc>) { + async fn on_room_avatar(&self, _: &Room, _: &AvatarEvent) { self.0.lock().await.push("avatar".to_string()) } - async fn on_room_message(&mut self, _: Arc>, _: Arc>) { + async fn on_room_message(&self, _: &Room, _: &MessageEvent) { self.0.lock().await.push("message".to_string()) } - async fn on_room_message_feedback( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_room_message_feedback(&self, _: &Room, _: &FeedbackEvent) { self.0.lock().await.push("feedback".to_string()) } - async fn on_room_redaction(&mut self, _: Arc>, _: Arc>) { + async fn on_room_redaction(&self, _: &Room, _: &RedactionEvent) { self.0.lock().await.push("redaction".to_string()) } - async fn on_room_power_levels( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_room_power_levels(&self, _: &Room, _: &PowerLevelsEvent) { self.0.lock().await.push("power".to_string()) } - async fn on_state_member(&mut self, _: Arc>, _: Arc>) { + async fn on_state_member(&self, _: &Room, _: &MemberEvent) { self.0.lock().await.push("state member".to_string()) } - async fn on_state_name(&mut self, _: Arc>, _: Arc>) { + async fn on_state_name(&self, _: &Room, _: &NameEvent) { self.0.lock().await.push("state name".to_string()) } - async fn on_state_canonical_alias( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_state_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) { self.0.lock().await.push("state canonical".to_string()) } - async fn on_state_aliases(&mut self, _: Arc>, _: Arc>) { + async fn on_state_aliases(&self, _: &Room, _: &AliasesEvent) { self.0.lock().await.push("state aliases".to_string()) } - async fn on_state_avatar(&mut self, _: Arc>, _: Arc>) { + async fn on_state_avatar(&self, _: &Room, _: &AvatarEvent) { self.0.lock().await.push("state avatar".to_string()) } - async fn on_state_power_levels( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_state_power_levels(&self, _: &Room, _: &PowerLevelsEvent) { self.0.lock().await.push("state power".to_string()) } - async fn on_state_join_rules( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_state_join_rules(&self, _: &Room, _: &JoinRulesEvent) { self.0.lock().await.push("state rules".to_string()) } - async fn on_account_presence(&mut self, _: Arc>, _: Arc>) { + async fn on_account_presence(&self, _: &Room, _: &PresenceEvent) { self.0.lock().await.push("account presence".to_string()) } - async fn on_account_ignored_users( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_account_ignored_users(&self, _: &Room, _: &IgnoredUserListEvent) { self.0.lock().await.push("account ignore".to_string()) } - async fn on_account_push_rules( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_account_push_rules(&self, _: &Room, _: &PushRulesEvent) { self.0.lock().await.push("".to_string()) } - async fn on_account_data_fully_read( - &mut self, - _: Arc>, - _: Arc>, - ) { + async fn on_account_data_fully_read(&self, _: &Room, _: &FullyReadEvent) { self.0.lock().await.push("account read".to_string()) } - async fn on_presence_event(&mut self, _: Arc>, _: Arc>) { + async fn on_presence_event(&self, _: &Room, _: &PresenceEvent) { self.0.lock().await.push("presence event".to_string()) } } @@ -303,11 +234,9 @@ mod test { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); - let emitter = Arc::new(Mutex::new( - Box::new(EvEmitterTest(vec)) as Box<(dyn EventEmitter)> - )); + let emitter = Box::new(EvEmitterTest(vec)) as Box<(dyn EventEmitter)>; let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); - client.add_event_emitter(Arc::clone(&emitter)).await; + client.add_event_emitter(emitter).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); let _response = client.sync(sync_settings).await.unwrap(); diff --git a/src/models/room.rs b/src/models/room.rs index f307ec93..b0f1b735 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -427,7 +427,7 @@ mod test { let room = &rooms .get(&RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap()) .unwrap() - .lock() + .read() .await; assert_eq!(2, room.members.len()); From 6d1cbcd9ca51cfcd5cad3cb5f8a1f6ea2c5c824d Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 14 Apr 2020 18:14:14 -0400 Subject: [PATCH 17/29] remove runtime and clean up example --- examples/command_bot.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 222b4e34..c3004280 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -1,5 +1,3 @@ -use std::ops::Deref; -use std::sync::Arc; use std::{env, process::exit}; use url::Url; @@ -8,13 +6,10 @@ use matrix_sdk::{ events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; -use tokio::runtime::{Handle, Runtime}; -use tokio::sync::mpsc::{channel, Sender}; use tokio::sync::Mutex; struct CommandBot { client: Mutex, - // sender: Sender<(RoomId, MessageEventContent)> } impl CommandBot { @@ -66,7 +61,6 @@ async fn login_and_sync( homeserver_url: String, username: String, password: String, - exec: Handle, ) -> Result<(), matrix_sdk::Error> { let client_config = AsyncClientConfig::new(); // .proxy("http://localhost:8080")? @@ -89,16 +83,13 @@ async fn login_and_sync( println!("logged in as {}", username); - exec.spawn(async move { - client.sync_forever(SyncSettings::new(), |_| async {}).await; - }) - .await - .unwrap(); + client.sync_forever(SyncSettings::new(), |_| async {}).await; Ok(()) } -fn main() -> Result<(), matrix_sdk::Error> { +#[tokio::main] +async fn main() -> Result<(), matrix_sdk::Error> { let (homeserver_url, username, password) = match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { (Some(a), Some(b), Some(c)) => (a, b, c), @@ -111,15 +102,6 @@ fn main() -> Result<(), matrix_sdk::Error> { } }; - let mut runtime = tokio::runtime::Builder::new() - .basic_scheduler() - .threaded_scheduler() - .enable_all() - .build() - .unwrap(); - - let exec = runtime.handle().clone(); - - runtime.block_on(async { login_and_sync(homeserver_url, username, password, exec).await })?; + login_and_sync(homeserver_url, username, password).await?; Ok(()) } From 33a1b8b791644755a07541418fdad1e0b382c654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 12:44:00 +0200 Subject: [PATCH 18/29] crypto: Add more test for the Olm wrappers. --- src/crypto/olm.rs | 49 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index 1fed7697..f641ecb7 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -39,17 +39,15 @@ use crate::identifiers::RoomId; pub struct Account { inner: Arc>, identity_keys: Arc, - pub(crate) shared: Arc, + shared: Arc, } impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Olm Account: {:?}, shared: {}", - self.identity_keys(), - self.shared() - ) + f.debug_struct("Account") + .field("identity_keys", self.identity_keys()) + .field("shared", &self.shared()) + .finish() } } @@ -597,10 +595,12 @@ impl std::fmt::Debug for OutboundGroupSession { #[cfg(test)] mod test { - use crate::crypto::olm::Account; + use crate::crypto::olm::{Account, InboundGroupSession, OutboundGroupSession}; + use crate::identifiers::RoomId; use olm_rs::session::OlmMessage; use ruma_client_api::r0::keys::SignedKey; use std::collections::HashMap; + use std::convert::TryFrom; #[test] fn account_creation() { @@ -618,6 +618,9 @@ mod test { identyty_keys.get("ed25519").unwrap() ); assert!(!identyty_keys.curve25519().is_empty()); + + account.mark_as_shared(); + assert!(account.shared()); } #[tokio::test] @@ -694,4 +697,34 @@ mod test { let decyrpted = alice_session.decrypt(message).await.unwrap(); assert_eq!(plaintext, decyrpted); } + + #[tokio::test] + async fn group_session_creation() { + let alice = Account::new(); + let room_id = RoomId::try_from("!test:localhost").unwrap(); + + let outbound = OutboundGroupSession::new(&room_id); + + 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(plaintext.clone()).await; + + assert_eq!(plaintext, inbound.decrypt(ciphertext).await.unwrap().0); + } } From 49e962e9c4bcf2af103b6506315606daffaff723 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 06:58:25 -0400 Subject: [PATCH 19/29] event_emitter: use Arc, @@ -22,7 +24,7 @@ impl CommandBot { #[async_trait::async_trait] impl EventEmitter for CommandBot { - async fn on_room_message(&self, room: &Room, event: &MessageEvent) { + async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { let msg_body = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), .. @@ -40,7 +42,7 @@ impl EventEmitter for CommandBot { formatted_body: None, relates_to: None, }); - let room_id = &room.room_id; + let room_id = { room.read().await.room_id.clone() }; println!("sending"); @@ -62,9 +64,10 @@ async fn login_and_sync( username: String, password: String, ) -> Result<(), matrix_sdk::Error> { - let client_config = AsyncClientConfig::new(); - // .proxy("http://localhost:8080")? - // .disable_ssl_verification(); + let client_config = AsyncClientConfig::new() + .proxy("http://localhost:8080")? + .disable_ssl_verification(); + let homeserver_url = Url::parse(&homeserver_url)?; let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); diff --git a/examples/login.rs b/examples/login.rs index 34f943a2..703fd2e0 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,4 +1,3 @@ -use std::ops::Deref; use std::sync::Arc; use std::{env, process::exit}; use url::Url; @@ -8,25 +7,30 @@ use matrix_sdk::{ events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; -use tokio::sync::Mutex; +use tokio::sync::RwLock; struct EventCallback; #[async_trait::async_trait] impl EventEmitter for EventCallback { - async fn on_room_message(&self, room: &Room, event: &MessageEvent) { + async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), sender, .. } = event { - let member = room.members.get(&sender).unwrap(); - println!( - "{}: {}", - member.display_name.as_ref().unwrap_or(&sender.to_string()), - msg_body - ); + let name = { + // any reads or + let room = room.read().await; + let member = room.members.get(&sender).unwrap(); + member + .display_name + .as_ref() + .map(ToString::to_string) + .unwrap_or(sender.to_string()) + }; + println!("{}: {}", name, msg_body); } } } diff --git a/src/base_client.rs b/src/base_client.rs index 5c8a5bf3..686f8c15 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -17,7 +17,6 @@ use std::collections::HashMap; #[cfg(feature = "encryption")] use std::collections::HashSet; use std::fmt; -use std::ops::Deref; use std::sync::Arc; #[cfg(feature = "encryption")] @@ -578,21 +577,21 @@ impl Client { RoomEvent::RoomMember(mem) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_member(room.read().await.deref(), &mem).await; + ee.on_room_member(Arc::clone(&room), &mem).await; } } } RoomEvent::RoomName(name) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_name(room.read().await.deref(), &name).await; + ee.on_room_name(Arc::clone(&room), &name).await; } } } RoomEvent::RoomCanonicalAlias(canonical) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_canonical_alias(room.read().await.deref(), &canonical) + ee.on_room_canonical_alias(Arc::clone(&room), &canonical) .await; } } @@ -600,29 +599,28 @@ impl Client { RoomEvent::RoomAliases(aliases) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_aliases(room.read().await.deref(), &aliases) - .await; + ee.on_room_aliases(Arc::clone(&room), &aliases).await; } } } RoomEvent::RoomAvatar(avatar) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_avatar(room.read().await.deref(), &avatar).await; + ee.on_room_avatar(Arc::clone(&room), &avatar).await; } } } RoomEvent::RoomMessage(msg) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_message(room.read().await.deref(), &msg).await; + ee.on_room_message(Arc::clone(&room), &msg).await; } } } RoomEvent::RoomMessageFeedback(msg_feedback) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_message_feedback(room.read().await.deref(), &msg_feedback) + ee.on_room_message_feedback(Arc::clone(&room), &msg_feedback) .await; } } @@ -630,16 +628,14 @@ impl Client { RoomEvent::RoomRedaction(redaction) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_redaction(room.read().await.deref(), &redaction) - .await; + ee.on_room_redaction(Arc::clone(&room), &redaction).await; } } } RoomEvent::RoomPowerLevels(power) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_room_power_levels(room.read().await.deref(), &power) - .await; + ee.on_room_power_levels(Arc::clone(&room), &power).await; } } } @@ -652,21 +648,21 @@ impl Client { StateEvent::RoomMember(member) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_member(room.read().await.deref(), &member).await; + ee.on_state_member(Arc::clone(&room), &member).await; } } } StateEvent::RoomName(name) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_name(room.read().await.deref(), &name).await; + ee.on_state_name(Arc::clone(&room), &name).await; } } } StateEvent::RoomCanonicalAlias(canonical) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_canonical_alias(room.read().await.deref(), &canonical) + ee.on_state_canonical_alias(Arc::clone(&room), &canonical) .await; } } @@ -674,31 +670,28 @@ impl Client { StateEvent::RoomAliases(aliases) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_aliases(room.read().await.deref(), &aliases) - .await; + ee.on_state_aliases(Arc::clone(&room), &aliases).await; } } } StateEvent::RoomAvatar(avatar) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_avatar(room.read().await.deref(), &avatar).await; + ee.on_state_avatar(Arc::clone(&room), &avatar).await; } } } StateEvent::RoomPowerLevels(power) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_power_levels(room.read().await.deref(), &power) - .await; + ee.on_state_power_levels(Arc::clone(&room), &power).await; } } } StateEvent::RoomJoinRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_state_join_rules(room.read().await.deref(), &rules) - .await; + ee.on_state_join_rules(Arc::clone(&room), &rules).await; } } } @@ -711,15 +704,14 @@ impl Client { NonRoomEvent::Presence(presence) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_presence(room.read().await.deref(), &presence) - .await; + ee.on_account_presence(Arc::clone(&room), &presence).await; } } } NonRoomEvent::IgnoredUserList(ignored) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_ignored_users(room.read().await.deref(), &ignored) + ee.on_account_ignored_users(Arc::clone(&room), &ignored) .await; } } @@ -727,15 +719,14 @@ impl Client { NonRoomEvent::PushRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_push_rules(room.read().await.deref(), &rules) - .await; + ee.on_account_push_rules(Arc::clone(&room), &rules).await; } } } NonRoomEvent::FullyRead(full_read) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_data_fully_read(room.read().await.deref(), &full_read) + ee.on_account_data_fully_read(Arc::clone(&room), &full_read) .await; } } @@ -749,15 +740,14 @@ impl Client { NonRoomEvent::Presence(presence) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_presence(room.read().await.deref(), &presence) - .await; + ee.on_account_presence(Arc::clone(&room), &presence).await; } } } NonRoomEvent::IgnoredUserList(ignored) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_ignored_users(room.read().await.deref(), &ignored) + ee.on_account_ignored_users(Arc::clone(&room), &ignored) .await; } } @@ -765,15 +755,14 @@ impl Client { NonRoomEvent::PushRules(rules) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_push_rules(room.read().await.deref(), &rules) - .await; + ee.on_account_push_rules(Arc::clone(&room), &rules).await; } } } NonRoomEvent::FullyRead(full_read) => { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_account_data_fully_read(room.read().await.deref(), &full_read) + ee.on_account_data_fully_read(Arc::clone(&room), &full_read) .await; } } @@ -785,8 +774,7 @@ impl Client { pub(crate) async fn emit_presence_event(&self, room_id: &RoomId, event: &PresenceEvent) { if let Some(ee) = &self.event_emitter { if let Some(room) = self.get_room(&room_id) { - ee.on_presence_event(room.read().await.deref(), &event) - .await; + ee.on_presence_event(Arc::clone(&room), &event).await; } } } diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index 6ee62d18..f3afd392 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -12,6 +12,7 @@ // 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 std::sync::Arc; use crate::events::{ fully_read::FullyReadEvent, @@ -31,6 +32,7 @@ use crate::events::{ }, }; use crate::models::Room; +use tokio::sync::RwLock; /// This trait allows any type implementing `EventEmitter` to specify event callbacks for each event. /// The `AsyncClient` calls each method when the corresponding event is received. @@ -78,53 +80,53 @@ use crate::models::Room; pub trait EventEmitter: Send + Sync { // ROOM EVENTS from `IncomingTimeline` /// Fires when `AsyncClient` receives a `RoomEvent::RoomMember` event. - async fn on_room_member(&self, _: &Room, _: &MemberEvent) {} + async fn on_room_member(&self, _: Arc>, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomName` event. - async fn on_room_name(&self, _: &Room, _: &NameEvent) {} + async fn on_room_name(&self, _: Arc>, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomCanonicalAlias` event. - async fn on_room_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) {} + async fn on_room_canonical_alias(&self, _: Arc>, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAliases` event. - async fn on_room_aliases(&self, _: &Room, _: &AliasesEvent) {} + async fn on_room_aliases(&self, _: Arc>, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomAvatar` event. - async fn on_room_avatar(&self, _: &Room, _: &AvatarEvent) {} + async fn on_room_avatar(&self, _: Arc>, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessage` event. - async fn on_room_message(&self, _: &Room, _: &MessageEvent) {} + async fn on_room_message(&self, _: Arc>, _: &MessageEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomMessageFeedback` event. - async fn on_room_message_feedback(&self, _: &Room, _: &FeedbackEvent) {} + async fn on_room_message_feedback(&self, _: Arc>, _: &FeedbackEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomRedaction` event. - async fn on_room_redaction(&self, _: &Room, _: &RedactionEvent) {} + async fn on_room_redaction(&self, _: Arc>, _: &RedactionEvent) {} /// Fires when `AsyncClient` receives a `RoomEvent::RoomPowerLevels` event. - async fn on_room_power_levels(&self, _: &Room, _: &PowerLevelsEvent) {} + async fn on_room_power_levels(&self, _: Arc>, _: &PowerLevelsEvent) {} // `RoomEvent`s from `IncomingState` /// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event. - async fn on_state_member(&self, _: &Room, _: &MemberEvent) {} + async fn on_state_member(&self, _: Arc>, _: &MemberEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomName` event. - async fn on_state_name(&self, _: &Room, _: &NameEvent) {} + async fn on_state_name(&self, _: Arc>, _: &NameEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomCanonicalAlias` event. - async fn on_state_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) {} + async fn on_state_canonical_alias(&self, _: Arc>, _: &CanonicalAliasEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAliases` event. - async fn on_state_aliases(&self, _: &Room, _: &AliasesEvent) {} + async fn on_state_aliases(&self, _: Arc>, _: &AliasesEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomAvatar` event. - async fn on_state_avatar(&self, _: &Room, _: &AvatarEvent) {} + async fn on_state_avatar(&self, _: Arc>, _: &AvatarEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomPowerLevels` event. - async fn on_state_power_levels(&self, _: &Room, _: &PowerLevelsEvent) {} + async fn on_state_power_levels(&self, _: Arc>, _: &PowerLevelsEvent) {} /// Fires when `AsyncClient` receives a `StateEvent::RoomJoinRules` event. - async fn on_state_join_rules(&self, _: &Room, _: &JoinRulesEvent) {} + async fn on_state_join_rules(&self, _: Arc>, _: &JoinRulesEvent) {} // `NonRoomEvent` (this is a type alias from ruma_events) from `IncomingAccountData` /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomMember` event. - async fn on_account_presence(&self, _: &Room, _: &PresenceEvent) {} + async fn on_account_presence(&self, _: Arc>, _: &PresenceEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomName` event. - async fn on_account_ignored_users(&self, _: &Room, _: &IgnoredUserListEvent) {} + async fn on_account_ignored_users(&self, _: Arc>, _: &IgnoredUserListEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomCanonicalAlias` event. - async fn on_account_push_rules(&self, _: &Room, _: &PushRulesEvent) {} + async fn on_account_push_rules(&self, _: Arc>, _: &PushRulesEvent) {} /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_account_data_fully_read(&self, _: &Room, _: &FullyReadEvent) {} + async fn on_account_data_fully_read(&self, _: Arc>, _: &FullyReadEvent) {} // `PresenceEvent` is a struct so there is only the one method /// Fires when `AsyncClient` receives a `NonRoomEvent::RoomAliases` event. - async fn on_presence_event(&self, _: &Room, _: &PresenceEvent) {} + async fn on_presence_event(&self, _: Arc>, _: &PresenceEvent) {} } #[cfg(test)] @@ -137,69 +139,69 @@ mod test { #[async_trait::async_trait] impl EventEmitter for EvEmitterTest { - async fn on_room_member(&self, _: &Room, _: &MemberEvent) { + async fn on_room_member(&self, _: Arc>, _: &MemberEvent) { self.0.lock().await.push("member".to_string()) } - async fn on_room_name(&self, _: &Room, _: &NameEvent) { + async fn on_room_name(&self, _: Arc>, _: &NameEvent) { self.0.lock().await.push("name".to_string()) } - async fn on_room_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) { + async fn on_room_canonical_alias(&self, _: Arc>, _: &CanonicalAliasEvent) { self.0.lock().await.push("canonical".to_string()) } - async fn on_room_aliases(&self, _: &Room, _: &AliasesEvent) { + async fn on_room_aliases(&self, _: Arc>, _: &AliasesEvent) { self.0.lock().await.push("aliases".to_string()) } - async fn on_room_avatar(&self, _: &Room, _: &AvatarEvent) { + async fn on_room_avatar(&self, _: Arc>, _: &AvatarEvent) { self.0.lock().await.push("avatar".to_string()) } - async fn on_room_message(&self, _: &Room, _: &MessageEvent) { + async fn on_room_message(&self, _: Arc>, _: &MessageEvent) { self.0.lock().await.push("message".to_string()) } - async fn on_room_message_feedback(&self, _: &Room, _: &FeedbackEvent) { + async fn on_room_message_feedback(&self, _: Arc>, _: &FeedbackEvent) { self.0.lock().await.push("feedback".to_string()) } - async fn on_room_redaction(&self, _: &Room, _: &RedactionEvent) { + async fn on_room_redaction(&self, _: Arc>, _: &RedactionEvent) { self.0.lock().await.push("redaction".to_string()) } - async fn on_room_power_levels(&self, _: &Room, _: &PowerLevelsEvent) { + async fn on_room_power_levels(&self, _: Arc>, _: &PowerLevelsEvent) { self.0.lock().await.push("power".to_string()) } - async fn on_state_member(&self, _: &Room, _: &MemberEvent) { + async fn on_state_member(&self, _: Arc>, _: &MemberEvent) { self.0.lock().await.push("state member".to_string()) } - async fn on_state_name(&self, _: &Room, _: &NameEvent) { + async fn on_state_name(&self, _: Arc>, _: &NameEvent) { self.0.lock().await.push("state name".to_string()) } - async fn on_state_canonical_alias(&self, _: &Room, _: &CanonicalAliasEvent) { + async fn on_state_canonical_alias(&self, _: Arc>, _: &CanonicalAliasEvent) { self.0.lock().await.push("state canonical".to_string()) } - async fn on_state_aliases(&self, _: &Room, _: &AliasesEvent) { + async fn on_state_aliases(&self, _: Arc>, _: &AliasesEvent) { self.0.lock().await.push("state aliases".to_string()) } - async fn on_state_avatar(&self, _: &Room, _: &AvatarEvent) { + async fn on_state_avatar(&self, _: Arc>, _: &AvatarEvent) { self.0.lock().await.push("state avatar".to_string()) } - async fn on_state_power_levels(&self, _: &Room, _: &PowerLevelsEvent) { + async fn on_state_power_levels(&self, _: Arc>, _: &PowerLevelsEvent) { self.0.lock().await.push("state power".to_string()) } - async fn on_state_join_rules(&self, _: &Room, _: &JoinRulesEvent) { + async fn on_state_join_rules(&self, _: Arc>, _: &JoinRulesEvent) { self.0.lock().await.push("state rules".to_string()) } - async fn on_account_presence(&self, _: &Room, _: &PresenceEvent) { + async fn on_account_presence(&self, _: Arc>, _: &PresenceEvent) { self.0.lock().await.push("account presence".to_string()) } - async fn on_account_ignored_users(&self, _: &Room, _: &IgnoredUserListEvent) { + async fn on_account_ignored_users(&self, _: Arc>, _: &IgnoredUserListEvent) { self.0.lock().await.push("account ignore".to_string()) } - async fn on_account_push_rules(&self, _: &Room, _: &PushRulesEvent) { + async fn on_account_push_rules(&self, _: Arc>, _: &PushRulesEvent) { self.0.lock().await.push("".to_string()) } - async fn on_account_data_fully_read(&self, _: &Room, _: &FullyReadEvent) { + async fn on_account_data_fully_read(&self, _: Arc>, _: &FullyReadEvent) { self.0.lock().await.push("account read".to_string()) } - async fn on_presence_event(&self, _: &Room, _: &PresenceEvent) { + async fn on_presence_event(&self, _: Arc>, _: &PresenceEvent) { self.0.lock().await.push("presence event".to_string()) } } From 53a2b8eb7c8812e5e4b3df5e9cf05ee102a066b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 13:46:43 +0200 Subject: [PATCH 20/29] crypto: Implement a better debug trait for sessions. --- src/crypto/olm.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index f641ecb7..72e28b2d 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -232,7 +232,7 @@ impl PartialEq for Account { /// /// Sessions are used to exchange encrypted messages between two /// accounts/devices. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Session { inner: Arc>, session_id: Arc, @@ -241,6 +241,15 @@ pub struct Session { pub(crate) last_use_time: Arc, } +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. /// @@ -688,10 +697,15 @@ mod test { let bob_keys = bob.identity_keys(); let mut alice_session = alice - .create_inbound_session(bob_keys.curve25519(), prekey_message) + .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(); From 3f9243a3261faeddf5d0619ecfb5c36dcf4b93f4 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 07:52:29 -0400 Subject: [PATCH 21/29] command-bot: add comments, use timestamp to filter old messages --- examples/command_bot.rs | 31 ++++++++++++++++++++++--------- examples/login.rs | 3 ++- src/async_client.rs | 2 ++ src/event_emitter/mod.rs | 23 ++++++++++++----------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 56925416..2f2213e5 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -1,23 +1,31 @@ use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, process::exit}; -use url::Url; - +use js_int::UInt; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; use tokio::sync::{Mutex, RwLock}; +use url::Url; struct CommandBot { + /// This clone of the `AsyncClient` will send requests to the server, + /// while the other keeps us in sync with the server using `sync_forever`. client: Mutex, + /// A timestamp so we only respond to messages sent after the bot is running. + start_time: UInt, } impl CommandBot { pub fn new(client: AsyncClient) -> Self { + let now = SystemTime::now(); + let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_millis(); Self { client: Mutex::new(client), + start_time: UInt::new(timestamp as u64).unwrap(), } } } @@ -25,30 +33,34 @@ impl CommandBot { #[async_trait::async_trait] impl EventEmitter for CommandBot { async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { - let msg_body = if let MessageEvent { + let (msg_body, timestamp) = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), + origin_server_ts, .. } = event { - msg_body.clone() + (msg_body.clone(), *origin_server_ts) } else { - String::new() + (String::new(), UInt::min_value()) }; - if msg_body.contains("!party") { + if msg_body.contains("!party") && timestamp > self.start_time { let content = MessageEventContent::Text(TextMessageEventContent { body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), format: None, formatted_body: None, relates_to: None, }); - let room_id = { room.read().await.room_id.clone() }; + // we clone here to hold the lock for as little time as possible. + let room_id = room.read().await.room_id.clone(); println!("sending"); self.client .lock() .await + // send our message to the room we found the "!party" command in + // the last parameter is an optional Uuid which we don't care about. .room_send(&room_id, content, None) .await .unwrap(); @@ -58,7 +70,6 @@ impl EventEmitter for CommandBot { } } -#[allow(clippy::for_loop_over_option)] async fn login_and_sync( homeserver_url: String, username: String, @@ -69,8 +80,9 @@ async fn login_and_sync( .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url)?; + // create a new AsyncClient with the given homeserver url and config let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); - + // add our CommandBot to be notified of incoming messages client .add_event_emitter(Box::new(CommandBot::new(client.clone()))) .await; @@ -86,6 +98,7 @@ async fn login_and_sync( println!("logged in as {}", username); + // this keeps state from the server streaming in to CommandBot via the EventEmitter trait client.sync_forever(SyncSettings::new(), |_| async {}).await; Ok(()) diff --git a/examples/login.rs b/examples/login.rs index 703fd2e0..150684c7 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -21,7 +21,8 @@ impl EventEmitter for EventCallback { } = event { let name = { - // any reads or + // any reads should be held for the shortest time possible to + // avoid dead locks let room = room.read().await; let member = room.members.get(&sender).unwrap(); member diff --git a/src/async_client.rs b/src/async_client.rs index d52b724c..86a9e139 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -53,6 +53,8 @@ const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone)] /// An async/await enabled Matrix client. +/// +/// All of the state is held in an `Arc` so the `AsyncClient` can be cloned freely. pub struct AsyncClient { /// The URL of the homeserver to connect to. homeserver: Url, diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index f3afd392..2d675521 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -50,28 +50,29 @@ use tokio::sync::RwLock; /// }, /// AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, /// }; -/// use tokio::sync::Mutex; +/// use tokio::sync::RwLock; /// /// struct EventCallback; /// /// #[async_trait::async_trait] /// impl EventEmitter for EventCallback { -/// async fn on_room_message(&self, room: &Room, event: &MessageEvent) { +/// async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { /// if let MessageEvent { /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// sender, /// .. /// } = event /// { -/// let member = room.members.get(&sender).unwrap(); -/// println!( -/// "{}: {}", -/// member -/// .display_name -/// .as_ref() -/// .unwrap_or(&sender.to_string()), -/// msg_body -/// ); +/// let name = { +/// let room = room.read().await; +/// let member = room.members.get(&sender).unwrap(); +/// member +/// .display_name +/// .as_ref() +/// .map(ToString::to_string) +/// .unwrap_or(sender.to_string()) +/// }; +/// println!("{}: {}", name, msg_body); /// } /// } /// } From 202ab9b0505ab0111f37af9bac1d107298f7c114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 14:20:34 +0200 Subject: [PATCH 22/29] travis: Disable the chache for now as it seems to time out. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 56b1810a..12b132e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ addons: packages: - libssl-dev -cache: cargo - jobs: include: - os: linux From c495a50c52c95a9545630153848dc5513cd3159f Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 08:29:34 -0400 Subject: [PATCH 23/29] async_client: make pub API take &self instead of &mut, use read where possible --- examples/command_bot.rs | 20 +++++++++++--------- src/async_client.rs | 29 +++++++++++++---------------- src/base_client.rs | 2 +- src/models/room.rs | 2 +- src/request_builder.rs | 4 ++-- tests/async_client_tests.rs | 6 +++--- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 2f2213e5..20bbf7a1 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -8,13 +8,13 @@ use matrix_sdk::{ events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::RwLock; use url::Url; struct CommandBot { /// This clone of the `AsyncClient` will send requests to the server, /// while the other keeps us in sync with the server using `sync_forever`. - client: Mutex, + client: AsyncClient, /// A timestamp so we only respond to messages sent after the bot is running. start_time: UInt, } @@ -24,7 +24,7 @@ impl CommandBot { let now = SystemTime::now(); let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_millis(); Self { - client: Mutex::new(client), + client, start_time: UInt::new(timestamp as u64).unwrap(), } } @@ -57,8 +57,6 @@ impl EventEmitter for CommandBot { println!("sending"); self.client - .lock() - .await // send our message to the room we found the "!party" command in // the last parameter is an optional Uuid which we don't care about. .room_send(&room_id, content, None) @@ -82,10 +80,6 @@ async fn login_and_sync( let homeserver_url = Url::parse(&homeserver_url)?; // create a new AsyncClient with the given homeserver url and config let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); - // add our CommandBot to be notified of incoming messages - client - .add_event_emitter(Box::new(CommandBot::new(client.clone()))) - .await; client .login( @@ -98,6 +92,14 @@ async fn login_and_sync( println!("logged in as {}", username); + // initial sync to set up state and so our bot doesn't respond to old messages + client.sync(SyncSettings::default()).await.unwrap(); + // add our CommandBot to be notified of incoming messages, we do this after the initial + // sync to avoid responding to messages before the bot was running. + client + .add_event_emitter(Box::new(CommandBot::new(client.clone()))) + .await; + // this keeps state from the server streaming in to CommandBot via the EventEmitter trait client.sync_forever(SyncSettings::new(), |_| async {}).await; diff --git a/src/async_client.rs b/src/async_client.rs index 86a9e139..5e298a8b 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -318,7 +318,7 @@ impl AsyncClient { /// only if the client also holds the encryption keys for this device. #[instrument(skip(password))] pub async fn login + std::fmt::Debug>( - &mut self, + &self, user: S, password: S, device_id: Option, @@ -368,7 +368,7 @@ impl AsyncClient { /// * alias - The `RoomId` or `RoomAliasId` of the room to be joined. /// An alias looks like this `#name:example.com` pub async fn join_room_by_id_or_alias( - &mut self, + &self, alias: &RoomIdOrAliasId, ) -> Result { let request = join_room_by_id_or_alias::Request { @@ -390,7 +390,7 @@ impl AsyncClient { /// /// * reason - Optional reason why the room member is being kicked out. pub async fn kick_user( - &mut self, + &self, room_id: &RoomId, user_id: &UserId, reason: Option, @@ -411,7 +411,7 @@ impl AsyncClient { /// /// * room_id - The `RoomId` of the room to leave. /// - pub async fn leave_room(&mut self, room_id: &RoomId) -> Result { + pub async fn leave_room(&self, room_id: &RoomId) -> Result { let request = leave_room::Request { room_id: room_id.clone(), }; @@ -428,7 +428,7 @@ impl AsyncClient { /// /// * user_id - The `UserId` of the user to invite to the room. pub async fn invite_user_by_id( - &mut self, + &self, room_id: &RoomId, user_id: &UserId, ) -> Result { @@ -451,7 +451,7 @@ impl AsyncClient { /// /// * invite_id - A third party id of a user to invite to the room. pub async fn invite_user_by_3pid( - &mut self, + &self, room_id: &RoomId, invite_id: &Invite3pid, ) -> Result { @@ -492,7 +492,7 @@ impl AsyncClient { /// # }); /// ``` pub async fn create_room>( - &mut self, + &self, room: R, ) -> Result { let request = room.into(); @@ -536,7 +536,7 @@ impl AsyncClient { /// # }); /// ``` pub async fn room_messages>( - &mut self, + &self, request: R, ) -> Result { let req = request.into(); @@ -549,10 +549,7 @@ impl AsyncClient { /// /// * `sync_settings` - Settings for the sync call. #[instrument] - pub async fn sync( - &mut self, - sync_settings: SyncSettings, - ) -> Result { + pub async fn sync(&self, sync_settings: SyncSettings) -> Result { let request = sync_events::Request { filter: None, since: sync_settings.token, @@ -701,7 +698,7 @@ impl AsyncClient { /// ``` #[instrument(skip(callback))] pub async fn sync_forever( - &mut self, + &self, sync_settings: SyncSettings, callback: impl Fn(sync_events::IncomingResponse) -> C + Send, ) where @@ -879,7 +876,7 @@ impl AsyncClient { /// }) /// ``` pub async fn room_send( - &mut self, + &self, room_id: &RoomId, #[allow(unused_mut)] mut content: MessageEventContent, txn_id: Option, @@ -894,7 +891,7 @@ impl AsyncClient { let room = client.joined_rooms.get(room_id); match room { - Some(r) => r.write().await.is_encrypted(), + Some(r) => r.read().await.is_encrypted(), None => false, } }; @@ -903,7 +900,7 @@ impl AsyncClient { let missing_sessions = { let client = self.base_client.read().await; let room = client.joined_rooms.get(room_id); - let room = room.as_ref().unwrap().write().await; + let room = room.as_ref().unwrap().read().await; let users = room.members.keys(); self.base_client .read() diff --git a/src/base_client.rs b/src/base_client.rs index 686f8c15..33a0314f 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -811,7 +811,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/models/room.rs b/src/models/room.rs index b0f1b735..9701d04a 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -417,7 +417,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/request_builder.rs b/src/request_builder.rs index 42f088f5..795e8534 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -341,7 +341,7 @@ mod test { .room_alias_name("room_alias") .topic("room topic") .visibility(Visibility::Private); - let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::new(homeserver, Some(session)).unwrap(); assert!(cli.create_room(builder).await.is_ok()); } @@ -373,7 +373,7 @@ mod test { // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? // .filter(RoomEventFilter::default()); - let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::new(homeserver, Some(session)).unwrap(); assert!(cli.room_messages(builder).await.is_ok()); } } diff --git a/tests/async_client_tests.rs b/tests/async_client_tests.rs index b2e72ff7..3c9dacdc 100644 --- a/tests/async_client_tests.rs +++ b/tests/async_client_tests.rs @@ -17,7 +17,7 @@ async fn login() { .with_body_from_file("tests/data/login_response.json") .create(); - let mut client = AsyncClient::new(homeserver, None).unwrap(); + let client = AsyncClient::new(homeserver, None).unwrap(); client .login("example", "wordpass", None, None) @@ -46,7 +46,7 @@ async fn sync() { .with_body_from_file("tests/data/sync.json") .create(); - let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -75,7 +75,7 @@ async fn room_names() { .with_body_from_file("tests/data/sync.json") .create(); - let mut client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); From 63368a9437a4b1ec6cc8276678ddfb2258590ab1 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 08:44:29 -0400 Subject: [PATCH 24/29] command_bot: remove timestamp --- examples/command_bot.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 20bbf7a1..effd2e7e 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -1,8 +1,6 @@ use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, process::exit}; -use js_int::UInt; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, @@ -15,36 +13,28 @@ struct CommandBot { /// This clone of the `AsyncClient` will send requests to the server, /// while the other keeps us in sync with the server using `sync_forever`. client: AsyncClient, - /// A timestamp so we only respond to messages sent after the bot is running. - start_time: UInt, } impl CommandBot { pub fn new(client: AsyncClient) -> Self { - let now = SystemTime::now(); - let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_millis(); - Self { - client, - start_time: UInt::new(timestamp as u64).unwrap(), - } + Self { client } } } #[async_trait::async_trait] impl EventEmitter for CommandBot { async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { - let (msg_body, timestamp) = if let MessageEvent { + let msg_body = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), - origin_server_ts, .. } = event { - (msg_body.clone(), *origin_server_ts) + msg_body.clone() } else { - (String::new(), UInt::min_value()) + String::new() }; - if msg_body.contains("!party") && timestamp > self.start_time { + if msg_body.contains("!party") { let content = MessageEventContent::Text(TextMessageEventContent { body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), format: None, From af73ebdf0907e3a66c60613b88fa27bbe51381e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 15:32:58 +0200 Subject: [PATCH 25/29] crypto: Add some tests to our in-memory stores. --- src/crypto/memory_stores.rs | 105 ++++++++++++++++++++++++++++++++++-- src/crypto/olm.rs | 6 +++ src/crypto/store/sqlite.rs | 4 +- 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/src/crypto/memory_stores.rs b/src/crypto/memory_stores.rs index 413e1e06..2d9f5e11 100644 --- a/src/crypto/memory_stores.rs +++ b/src/crypto/memory_stores.rs @@ -22,19 +22,22 @@ use super::device::Device; use super::olm::{InboundGroupSession, Session}; use crate::identifiers::{DeviceId, RoomId, UserId}; +/// In-memory store for Olm Sessions. #[derive(Debug)] pub struct SessionStore { entries: HashMap>>>, } impl SessionStore { + /// Create a new empty Session store. pub fn new() -> Self { SessionStore { entries: HashMap::new(), } } - pub async fn add(&mut self, session: Session) -> Session { + /// Add a session to the store. + pub async fn add(&mut self, session: Session) { if !self.entries.contains_key(&*session.sender_key) { self.entries.insert( session.sender_key.to_string(), @@ -42,15 +45,15 @@ impl SessionStore { ); } let sessions = self.entries.get_mut(&*session.sender_key).unwrap(); - sessions.lock().await.push(session.clone()); - - session + sessions.lock().await.push(session); } + /// Get all the sessions that belong to the given sender key. pub fn get(&self, sender_key: &str) -> Option>>> { self.entries.get(sender_key).cloned() } + /// Add a list of sessions belonging to the sender key. pub fn set_for_sender(&mut self, sender_key: &str, sessions: Vec) { self.entries .insert(sender_key.to_owned(), Arc::new(Mutex::new(sessions))); @@ -58,17 +61,20 @@ impl SessionStore { } #[derive(Debug)] +/// In-memory store that houlds inbound group sessions. pub struct GroupSessionStore { entries: HashMap>>, } impl GroupSessionStore { + /// Create a new empty store. pub fn new() -> Self { GroupSessionStore { entries: HashMap::new(), } } + /// Add a inbound group session to the store. pub fn add(&mut self, session: InboundGroupSession) -> bool { if !self.entries.contains_key(&session.room_id) { let room_id = &*session.room_id; @@ -88,6 +94,14 @@ impl GroupSessionStore { ret.is_some() } + /// Get a inbound group session from our store. + /// + /// # Arguments + /// * `room_id` - The room id of the room that the session belongs to. + /// + /// * `sender_key` - The sender key that sent us the session. + /// + /// * `session_id` - The unique id of the session. pub fn get( &self, room_id: &RoomId, @@ -158,3 +172,86 @@ impl DeviceStore { } } } + +#[cfg(test)] +mod test { + use std::collections::HashMap; + use std::convert::TryFrom; + + use crate::api::r0::keys::SignedKey; + use crate::crypto::memory_stores::{DeviceStore, GroupSessionStore, SessionStore}; + use crate::crypto::olm::{Account, InboundGroupSession, OutboundGroupSession, Session}; + use crate::identifiers::RoomId; + + async fn get_account_and_session() -> (Account, Session) { + let alice = Account::new(); + + let bob = Account::new(); + + bob.generate_one_time_keys(1).await; + let one_time_key = bob + .one_time_keys() + .await + .curve25519() + .iter() + .nth(0) + .unwrap() + .1 + .to_owned(); + let one_time_key = SignedKey { + key: one_time_key, + signatures: HashMap::new(), + }; + let sender_key = bob.identity_keys().curve25519().to_owned(); + let session = alice + .create_outbound_session(&sender_key, &one_time_key) + .await + .unwrap(); + + (alice, session) + } + + #[tokio::test] + async fn test_session_store() { + let (account, session) = get_account_and_session().await; + + let mut store = SessionStore::new(); + store.add(session.clone()).await; + + let sessions = store.get(&session.sender_key).unwrap(); + let sessions = sessions.lock().await; + + let loaded_session = &sessions[0]; + + assert_eq!(&session, loaded_session); + } + + #[tokio::test] + async fn test_group_session_store() { + let alice = Account::new(); + let room_id = RoomId::try_from("!test:localhost").unwrap(); + + let outbound = OutboundGroupSession::new(&room_id); + + 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(); + + let mut store = GroupSessionStore::new(); + store.add(inbound.clone()); + + let loaded_session = store + .get(&room_id, "test_key", outbound.session_id()) + .unwrap(); + assert_eq!(inbound, loaded_session); + } +} diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index 72e28b2d..8d160bbd 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -497,6 +497,12 @@ impl fmt::Debug for InboundGroupSession { } } +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 diff --git a/src/crypto/store/sqlite.rs b/src/crypto/store/sqlite.rs index cc4aea7c..99a859f4 100644 --- a/src/crypto/store/sqlite.rs +++ b/src/crypto/store/sqlite.rs @@ -350,7 +350,7 @@ impl CryptoStore for SqliteStore { } async fn add_and_save_session(&mut self, session: Session) -> Result<()> { - let session = self.sessions.add(session).await; + self.sessions.add(session.clone()).await; self.save_session(session).await?; Ok(()) } @@ -435,9 +435,7 @@ mod test { use olm_rs::outbound_group_session::OlmOutboundGroupSession; use ruma_client_api::r0::keys::SignedKey; use std::collections::HashMap; - use std::sync::Arc; use tempfile::tempdir; - use tokio::sync::Mutex; use super::{ Account, CryptoStore, InboundGroupSession, RoomId, Session, SqliteStore, TryFrom, UserId, From 9b52b58fea777426e5a8259eaf5ec664282e0c5c Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 09:54:53 -0400 Subject: [PATCH 26/29] command_bot: pass sync_token to sync_forever --- examples/command_bot.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index effd2e7e..220b0ea1 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -90,8 +90,11 @@ async fn login_and_sync( .add_event_emitter(Box::new(CommandBot::new(client.clone()))) .await; + // since we called sync before we `sync_forever` we must pass that sync token to + // `sync_forever` + let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); // this keeps state from the server streaming in to CommandBot via the EventEmitter trait - client.sync_forever(SyncSettings::new(), |_| async {}).await; + client.sync_forever(settings, |_| async {}).await; Ok(()) } From aeb93e19e25416e5e8946c3920b1fbca612e4a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 17:51:51 +0200 Subject: [PATCH 27/29] crypto: Test our in-memory device store. --- src/crypto/device.rs | 64 +++++++++++++++++++++++++++++++++++-- src/crypto/memory_stores.rs | 49 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/crypto/device.rs b/src/crypto/device.rs index c5d50a32..cfde349c 100644 --- a/src/crypto/device.rs +++ b/src/crypto/device.rs @@ -18,9 +18,9 @@ use std::sync::Arc; use atomic::Atomic; -use ruma_client_api::r0::keys::{DeviceKeys, KeyAlgorithm}; -use ruma_events::Algorithm; -use ruma_identifiers::{DeviceId, UserId}; +use crate::api::r0::keys::{DeviceKeys, KeyAlgorithm}; +use crate::events::Algorithm; +use crate::identifiers::{DeviceId, UserId}; #[derive(Debug, Clone)] pub struct Device { @@ -82,3 +82,61 @@ impl From<&DeviceKeys> for Device { } } } + +impl PartialEq for Device { + fn eq(&self, other: &Self) -> bool { + self.user_id() == other.user_id() && self.device_id() == other.device_id() + } +} + +#[cfg(test)] +pub(crate) mod test { + use serde_json::json; + use std::convert::{From, TryFrom}; + + use crate::api::r0::keys::DeviceKeys; + use crate::crypto::device::Device; + use crate::identifiers::UserId; + + pub(crate) fn get_device() -> Device { + let user_id = UserId::try_from("@alice:example.org").unwrap(); + let device_id = "DEVICEID"; + + let device_keys = json!({ + "algorithms": vec![ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": device_id, + "user_id": user_id.to_string(), + "keys": { + "curve25519:DEVICEID": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4", + "ed25519:DEVICEID": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + }, + "signatures": { + user_id.to_string(): { + "ed25519:DEVICEID": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA" + } + }, + "unsigned": { + "device_display_name": "Alice's mobile phone" + } + }); + + let device_keys: DeviceKeys = serde_json::from_value(device_keys).unwrap(); + + Device::from(&device_keys) + } + + #[test] + fn create_a_device() { + let user_id = UserId::try_from("@alice:example.org").unwrap(); + let device_id = "DEVICEID"; + + let device = get_device(); + + assert_eq!(&user_id, device.user_id()); + assert_eq!(device_id, device.device_id()); + assert_eq!(device.algorithms.len(), 2); + } +} diff --git a/src/crypto/memory_stores.rs b/src/crypto/memory_stores.rs index 2d9f5e11..b0e39bb6 100644 --- a/src/crypto/memory_stores.rs +++ b/src/crypto/memory_stores.rs @@ -114,36 +114,45 @@ impl GroupSessionStore { } } +/// In-memory store holding the devices of users. #[derive(Clone, Debug)] pub struct DeviceStore { entries: Arc>>, } +/// A read only view over all devices belonging to a user. pub struct UserDevices { entries: ReadOnlyView, } impl UserDevices { + /// Get the specific device with the given device id. pub fn get(&self, device_id: &str) -> Option { self.entries.get(device_id).cloned() } + /// Iterator over all the device ids of the user devices. pub fn keys(&self) -> impl Iterator { self.entries.keys() } + /// Iterator over all the devices of the user devices. pub fn devices(&self) -> impl Iterator { self.entries.values() } } impl DeviceStore { + /// Create a new empty device store. pub fn new() -> Self { DeviceStore { entries: Arc::new(DashMap::new()), } } + /// Add a device to the store. + /// + /// Returns true if the device was already in the store, false otherwise. pub fn add(&self, device: Device) -> bool { let user_id = device.user_id(); @@ -157,12 +166,14 @@ impl DeviceStore { .is_some() } + /// 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 { self.entries .get(user_id) .and_then(|m| m.get(device_id).map(|d| d.value().clone())) } + /// Get a read-only view over all devices of the given user. pub fn user_devices(&self, user_id: &UserId) -> UserDevices { if !self.entries.contains_key(user_id) { self.entries.insert(user_id.clone(), DashMap::new()); @@ -179,6 +190,7 @@ mod test { use std::convert::TryFrom; use crate::api::r0::keys::SignedKey; + use crate::crypto::device::test::get_device; use crate::crypto::memory_stores::{DeviceStore, GroupSessionStore, SessionStore}; use crate::crypto::olm::{Account, InboundGroupSession, OutboundGroupSession, Session}; use crate::identifiers::RoomId; @@ -226,6 +238,21 @@ mod test { assert_eq!(&session, loaded_session); } + #[tokio::test] + async fn test_session_store_bulk_storing() { + let (account, session) = get_account_and_session().await; + + let mut store = SessionStore::new(); + store.set_for_sender(&session.sender_key, vec![session.clone()]); + + let sessions = store.get(&session.sender_key).unwrap(); + let sessions = sessions.lock().await; + + let loaded_session = &sessions[0]; + + assert_eq!(&session, loaded_session); + } + #[tokio::test] async fn test_group_session_store() { let alice = Account::new(); @@ -254,4 +281,26 @@ mod test { .unwrap(); assert_eq!(inbound, loaded_session); } + + #[tokio::test] + async fn test_device_store() { + let device = get_device(); + let store = DeviceStore::new(); + + assert!(!store.add(device.clone())); + assert!(store.add(device.clone())); + + let loaded_device = store.get(device.user_id(), device.device_id()).unwrap(); + + assert_eq!(device, loaded_device); + + 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); + + let loaded_device = user_devices.get(device.device_id()).unwrap(); + + assert_eq!(device, loaded_device); + } } From 14f25bf66a597e927d5217c2243e1eb5db19609f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 18:22:04 +0200 Subject: [PATCH 28/29] crypto: Skip the coverage for debug implementations. --- src/crypto/olm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs index 8d160bbd..3dff2085 100644 --- a/src/crypto/olm.rs +++ b/src/crypto/olm.rs @@ -42,6 +42,7 @@ pub struct Account { shared: Arc, } +#[cfg_attr(tarpaulin, skip)] impl fmt::Debug for Account { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Account") @@ -241,6 +242,7 @@ pub struct Session { 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") @@ -489,6 +491,7 @@ impl InboundGroupSession { } } +#[cfg_attr(tarpaulin, skip)] impl fmt::Debug for InboundGroupSession { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("InboundGroupSession") @@ -597,6 +600,7 @@ impl OutboundGroupSession { } } +#[cfg_attr(tarpaulin, skip)] impl std::fmt::Debug for OutboundGroupSession { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OutboundGroupSession") From bedb788432ed2c66d2c592d2e4b8e6b800d5a79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 15 Apr 2020 18:55:15 +0200 Subject: [PATCH 29/29] crypto: Test the keys info for a device. --- src/crypto/device.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/crypto/device.rs b/src/crypto/device.rs index cfde349c..44bb8e3b 100644 --- a/src/crypto/device.rs +++ b/src/crypto/device.rs @@ -33,12 +33,6 @@ pub struct Device { trust_state: Arc>, } -impl Device { - pub fn keys(&self, algorithm: &KeyAlgorithm) -> Option<&String> { - self.keys.get(algorithm) - } -} - #[derive(Debug, Clone, Copy)] pub enum TrustState { Verified, @@ -55,6 +49,10 @@ impl Device { pub fn user_id(&self) -> &UserId { &self.user_id } + + pub fn keys(&self, algorithm: &KeyAlgorithm) -> Option<&String> { + self.keys.get(algorithm) + } } impl From<&DeviceKeys> for Device { @@ -94,7 +92,7 @@ pub(crate) mod test { use serde_json::json; use std::convert::{From, TryFrom}; - use crate::api::r0::keys::DeviceKeys; + use crate::api::r0::keys::{DeviceKeys, KeyAlgorithm}; use crate::crypto::device::Device; use crate::identifiers::UserId; @@ -138,5 +136,13 @@ pub(crate) mod test { assert_eq!(&user_id, device.user_id()); assert_eq!(device_id, device.device_id()); assert_eq!(device.algorithms.len(), 2); + assert_eq!( + device.keys(&KeyAlgorithm::Curve25519).unwrap(), + "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4" + ); + assert_eq!( + device.keys(&KeyAlgorithm::Ed25519).unwrap(), + "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM" + ); } }