diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index cf0592e2..b61aed6b 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -2393,7 +2393,7 @@ mod test { api::r0::{ account::register::Request as RegistrationRequest, directory::get_public_rooms_filtered::Request as PublicRoomsFilterRequest, - membership::Invite3pid, session::get_login_types::LoginType, uiaa::AuthData, + membership::Invite3pidInit, session::get_login_types::LoginType, uiaa::AuthData, }, assign, directory::Filter, @@ -2791,12 +2791,15 @@ mod test { let room = client.get_joined_room(&room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap(); - room.invite_user_by_3pid(Invite3pid { - id_server: "example.org", - id_access_token: "IdToken", - medium: thirdparty::Medium::Email, - address: "address", - }) + room.invite_user_by_3pid( + Invite3pidInit { + id_server: "example.org", + id_access_token: "IdToken", + medium: thirdparty::Medium::Email, + address: "address", + } + .into(), + ) .await .unwrap(); } @@ -3052,13 +3055,9 @@ mod test { let room = client.get_joined_room(&room_id).unwrap(); let avatar_url = mxc_uri!("mxc://example.org/avA7ar"); - let member_event = MemberEventContent { - avatar_url: Some(avatar_url), - membership: MembershipState::Join, - is_direct: None, - displayname: None, - third_party_invite: None, - }; + let member_event = assign!(MemberEventContent::new(MembershipState::Join), { + avatar_url: Some(avatar_url) + }); let content = AnyStateEventContent::RoomMember(member_event); let response = room.send_state_event(content, "").await.unwrap(); assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); diff --git a/matrix_sdk/src/room/common.rs b/matrix_sdk/src/room/common.rs index 80526bf2..82647506 100644 --- a/matrix_sdk/src/room/common.rs +++ b/matrix_sdk/src/room/common.rs @@ -245,7 +245,7 @@ impl Common { pub async fn joined_members_no_sync(&self) -> Result> { Ok(self .inner - .members() + .joined_members() .await? .into_iter() .map(|member| RoomMember::new(self.client.clone(), member)) diff --git a/matrix_sdk/src/room/joined.rs b/matrix_sdk/src/room/joined.rs index f28778e4..db10e91c 100644 --- a/matrix_sdk/src/room/joined.rs +++ b/matrix_sdk/src/room/joined.rs @@ -4,8 +4,6 @@ use std::{io::Read, ops::Deref}; #[cfg(feature = "encryption")] use matrix_sdk_base::crypto::AttachmentEncryptor; -#[cfg(feature = "encryption")] -use matrix_sdk_common::locks::Mutex; use matrix_sdk_common::{ api::r0::{ membership::{ @@ -36,6 +34,8 @@ use matrix_sdk_common::{ receipt::ReceiptType, uuid::Uuid, }; +#[cfg(feature = "encryption")] +use matrix_sdk_common::{events::room::EncryptedFileInit, locks::Mutex}; use mime::{self, Mime}; #[cfg(feature = "encryption")] use tracing::instrument; @@ -462,15 +462,18 @@ impl Joined { let response = self.client.upload(&content_type, &mut reader).await?; #[cfg(feature = "encryption")] - let keys = { + let keys: Option> = { let keys = reader.finish(); - Some(Box::new(EncryptedFile { - url: response.content_uri.clone(), - key: keys.web_key, - iv: keys.iv, - hashes: keys.hashes, - v: keys.version, - })) + Some(Box::new( + EncryptedFileInit { + url: response.content_uri.clone(), + key: keys.web_key, + iv: keys.iv, + hashes: keys.hashes, + v: keys.version, + } + .into(), + )) }; #[cfg(not(feature = "encryption"))] let keys: Option> = None; @@ -486,32 +489,23 @@ impl Joined { let content = match content_type.type_() { mime::IMAGE => { // TODO create a thumbnail using the image crate?. - MessageType::Image(ImageMessageEventContent { - body: body.to_owned(), - info: None, - url: Some(url), - file: encrypted_file, - }) + MessageType::Image(assign!( + ImageMessageEventContent::plain(body.to_owned(), url, None), + { file: encrypted_file } + )) } - mime::AUDIO => MessageType::Audio(AudioMessageEventContent { - body: body.to_owned(), - info: None, - url: Some(url), - file: encrypted_file, - }), - mime::VIDEO => MessageType::Video(VideoMessageEventContent { - body: body.to_owned(), - info: None, - url: Some(url), - file: encrypted_file, - }), - _ => MessageType::File(FileMessageEventContent { - filename: None, - body: body.to_owned(), - info: None, - url: Some(url), - file: encrypted_file, - }), + mime::AUDIO => MessageType::Audio(assign!( + AudioMessageEventContent::plain(body.to_owned(), url, None), + { file: encrypted_file } + )), + mime::VIDEO => MessageType::Video(assign!( + VideoMessageEventContent::plain(body.to_owned(), url, None), + { file: encrypted_file } + )), + _ => MessageType::File(assign!( + FileMessageEventContent::plain(body.to_owned(), url, None), + { file: encrypted_file } + )), }; self.send(AnyMessageEventContent::RoomMessage(MessageEventContent::new(content)), txn_id) @@ -540,6 +534,7 @@ impl Joined { /// room::member::{MemberEventContent, MembershipState}, /// }, /// identifiers::mxc_uri, + /// assign, /// }; /// # futures::executor::block_on(async { /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap(); @@ -547,13 +542,9 @@ impl Joined { /// # let room_id = matrix_sdk::identifiers::room_id!("!test:localhost"); /// /// let avatar_url = mxc_uri!("mxc://example.org/avatar"); - /// let member_event = MemberEventContent { + /// let member_event = assign!(MemberEventContent::new(MembershipState::Join), { /// avatar_url: Some(avatar_url), - /// membership: MembershipState::Join, - /// is_direct: None, - /// displayname: None, - /// third_party_invite: None, - /// }; + /// }); /// # let room = client /// # .get_joined_room(&room_id) /// # .unwrap(); diff --git a/matrix_sdk_appservice/examples/actix_autojoin.rs b/matrix_sdk_appservice/examples/actix_autojoin.rs index cc798966..ad385180 100644 --- a/matrix_sdk_appservice/examples/actix_autojoin.rs +++ b/matrix_sdk_appservice/examples/actix_autojoin.rs @@ -34,7 +34,9 @@ impl EventHandler for AppserviceEventHandler { if let MembershipState::Invite = event.content.membership { let user_id = UserId::try_from(event.state_key.clone()).unwrap(); - let client = self.appservice.client_with_localpart(user_id.localpart()).await.unwrap(); + self.appservice.register(user_id.localpart()).await.unwrap(); + + let client = self.appservice.client(Some(user_id.localpart())).await.unwrap(); client.join_room_by_id(room.room_id()).await.unwrap(); } @@ -55,7 +57,7 @@ pub async fn main() -> std::io::Result<()> { let event_handler = AppserviceEventHandler::new(appservice.clone()); - appservice.client().set_event_handler(Box::new(event_handler)).await; + appservice.set_event_handler(Box::new(event_handler)).await.unwrap(); HttpServer::new(move || App::new().service(appservice.actix_service())) .bind(("0.0.0.0", 8090))? diff --git a/matrix_sdk_appservice/src/actix.rs b/matrix_sdk_appservice/src/actix.rs index c0114440..bf5d2800 100644 --- a/matrix_sdk_appservice/src/actix.rs +++ b/matrix_sdk_appservice/src/actix.rs @@ -61,11 +61,11 @@ async fn push_transactions( request: IncomingRequest, appservice: Data, ) -> Result { - if !appservice.hs_token_matches(request.access_token) { + if !appservice.compare_hs_token(request.access_token) { return Ok(HttpResponse::Unauthorized().finish()); } - appservice.client().receive_transaction(request.incoming).await.unwrap(); + appservice.client(None).await?.receive_transaction(request.incoming).await?; Ok(HttpResponse::Ok().json("{}")) } @@ -76,7 +76,7 @@ async fn query_user_id( request: IncomingRequest, appservice: Data, ) -> Result { - if !appservice.hs_token_matches(request.access_token) { + if !appservice.compare_hs_token(request.access_token) { return Ok(HttpResponse::Unauthorized().finish()); } @@ -89,7 +89,7 @@ async fn query_room_alias( request: IncomingRequest, appservice: Data, ) -> Result { - if !appservice.hs_token_matches(request.access_token) { + if !appservice.compare_hs_token(request.access_token) { return Ok(HttpResponse::Unauthorized().finish()); } diff --git a/matrix_sdk_appservice/src/lib.rs b/matrix_sdk_appservice/src/lib.rs index 67b89385..11c9710d 100644 --- a/matrix_sdk_appservice/src/lib.rs +++ b/matrix_sdk_appservice/src/lib.rs @@ -20,13 +20,26 @@ //! the webserver for you //! * receive and validate requests from the homeserver correctly //! * allow calling the homeserver with proper virtual user identity assertion -//! * have the goal to have a consistent room state available by leveraging the -//! stores that the matrix-sdk provides +//! * have consistent room state by leveraging matrix-sdk's state store +//! * provide E2EE support by leveraging matrix-sdk's crypto store +//! +//! # Status +//! +//! The crate is in an experimental state. Follow +//! [matrix-org/matrix-rust-sdk#228] for progress. //! //! # Quickstart //! //! ```no_run //! # async { +//! # +//! # use matrix_sdk::{async_trait, EventHandler}; +//! # +//! # struct AppserviceEventHandler; +//! # +//! # #[async_trait] +//! # impl EventHandler for AppserviceEventHandler {} +//! # //! use matrix_sdk_appservice::{Appservice, AppserviceRegistration}; //! //! let homeserver_url = "http://127.0.0.1:8008"; @@ -42,17 +55,23 @@ //! users: //! - exclusive: true //! regex: '@_appservice_.*' -//! ") -//! .unwrap(); +//! ")?; //! -//! let appservice = Appservice::new(homeserver_url, server_name, registration).await.unwrap(); -//! // set event handler with `appservice.client().set_event_handler()` here -//! let (host, port) = appservice.get_host_and_port_from_registration().unwrap(); -//! appservice.run(host, port).await.unwrap(); +//! let appservice = Appservice::new(homeserver_url, server_name, registration).await?; +//! appservice.set_event_handler(Box::new(AppserviceEventHandler)).await?; +//! +//! let (host, port) = appservice.registration().get_host_and_port()?; +//! appservice.run(host, port).await?; +//! # +//! # Ok::<(), Box>(()) //! # }; //! ``` //! +//! Check the [examples directory] for fully working examples. +//! //! [Application Service]: https://matrix.org/docs/spec/application_service/r0.1.2 +//! [matrix-org/matrix-rust-sdk#228]: https://github.com/matrix-org/matrix-rust-sdk/issues/228 +//! [examples directory]: https://github.com/matrix-org/matrix-rust-sdk/tree/master/matrix_sdk_appservice/examples #[cfg(not(any(feature = "actix",)))] compile_error!("one webserver feature must be enabled. available ones: `actix`"); @@ -79,11 +98,10 @@ use matrix_sdk::{ assign, identifiers::{self, DeviceId, ServerNameBox, UserId}, reqwest::Url, - Client, ClientConfig, FromHttpResponseError, HttpError, RequestConfig, ServerError, Session, + Client, ClientConfig, EventHandler, FromHttpResponseError, HttpError, RequestConfig, + ServerError, Session, }; use regex::Regex; -#[cfg(not(feature = "actix"))] -use tracing::error; use tracing::warn; #[cfg(feature = "actix")] @@ -96,6 +114,8 @@ pub type Host = String; pub type Port = u16; /// Appservice Registration +/// +/// Wrapper around [`Registration`] #[derive(Debug, Clone)] pub struct AppserviceRegistration { inner: Registration, @@ -117,6 +137,26 @@ impl AppserviceRegistration { Ok(Self { inner: serde_yaml::from_reader(file)? }) } + + /// Get the host and port from the registration URL + /// + /// If no port is found it falls back to scheme defaults: 80 for http and + /// 443 for https + pub fn get_host_and_port(&self) -> Result<(Host, Port)> { + let uri = Uri::try_from(&self.inner.url)?; + + let host = uri.host().ok_or(Error::MissingRegistrationHost)?.to_owned(); + let port = match uri.port() { + Some(port) => Ok(port.as_u16()), + None => match uri.scheme_str() { + Some("http") => Ok(80), + Some("https") => Ok(443), + _ => Err(Error::MissingRegistrationPort), + }, + }?; + + Ok((host, port)) + } } impl From for AppserviceRegistration { @@ -133,31 +173,20 @@ impl Deref for AppserviceRegistration { } } -async fn create_client( - homeserver_url: &Url, - server_name: &ServerNameBox, +async fn client_session_with_login_restore( + client: &Client, registration: &AppserviceRegistration, - localpart: Option<&str>, -) -> Result { - let client = if localpart.is_some() { - let request_config = RequestConfig::default().assert_identity(); - let config = ClientConfig::default().request_config(request_config); - Client::new_with_config(homeserver_url.clone(), config)? - } else { - Client::new(homeserver_url.clone())? - }; - + localpart: impl AsRef + Into>, + server_name: &ServerNameBox, +) -> Result<()> { let session = Session { access_token: registration.as_token.clone(), - user_id: UserId::parse_with_server_name( - localpart.unwrap_or(®istration.sender_localpart), - &server_name, - )?, + user_id: UserId::parse_with_server_name(localpart, server_name)?, device_id: DeviceId::new(), }; client.restore_login(session).await?; - Ok(client) + Ok(()) } /// Appservice @@ -189,60 +218,82 @@ impl Appservice { let homeserver_url = homeserver_url.try_into()?; let server_name = server_name.try_into()?; - let client = create_client(&homeserver_url, &server_name, ®istration, None).await?; + let client_sender_localpart = Client::new(homeserver_url.clone())?; - Ok(Appservice { - homeserver_url, - server_name, - registration, - client_sender_localpart: client, - }) - } - - /// Get `Client` for the user associated with the application service - /// (`sender_localpart` of the [registration]) - /// - /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration - pub fn client(&self) -> Client { - self.client_sender_localpart.clone() - } - - /// Get `Client` for the given `localpart` - /// - /// If the `localpart` is covered by the `namespaces` in the [registration] - /// all requests to the homeserver will [assert the identity] to the - /// according virtual user. - /// - /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration - /// [assert the identity]: - /// https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion - pub async fn client_with_localpart( - &self, - localpart: impl AsRef + Into>, - ) -> Result { - let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?; - let localpart = user_id.localpart().to_owned(); - - let client = create_client( - &self.homeserver_url, - &self.server_name, - &self.registration, - Some(&localpart), + client_session_with_login_restore( + &client_sender_localpart, + ®istration, + registration.sender_localpart.as_ref(), + &server_name, ) .await?; - self.ensure_registered(localpart).await?; + Ok(Appservice { homeserver_url, server_name, registration, client_sender_localpart }) + } + + /// Get a [`Client`] + /// + /// Will return a `Client` that's configured to [assert the identity] on all + /// outgoing homeserver requests if `localpart` is given. If not given + /// the `Client` will use the main user associated with this appservice, + /// that is the `sender_localpart` in the [`AppserviceRegistration`] + /// + /// # Arguments + /// + /// * `localpart` - The localpart of the user we want assert our identity to + /// + /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration + /// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion + pub async fn client(&self, localpart: Option<&str>) -> Result { + let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref()); + + // The `as_token` in the `Session` maps to the main appservice user + // (`sender_localpart`) by default, so we don't need to assert identity + // in that case + let client = if localpart == self.registration.sender_localpart { + self.client_sender_localpart.clone() + } else { + let request_config = RequestConfig::default().assert_identity(); + let config = ClientConfig::default().request_config(request_config); + let client = Client::new_with_config(self.homeserver_url.clone(), config)?; + + client_session_with_login_restore( + &client, + &self.registration, + localpart, + &self.server_name, + ) + .await?; + + client + }; Ok(client) } - async fn ensure_registered(&self, localpart: impl AsRef) -> Result<()> { + /// Convenience wrapper around [`Client::set_event_handler()`] + pub async fn set_event_handler(&self, handler: Box) -> Result<()> { + let client = self.client(None).await?; + client.set_event_handler(handler).await; + + Ok(()) + } + + /// Register a virtual user by sending a [`RegistrationRequest`] to the + /// homeserver + /// + /// # Arguments + /// + /// * `localpart` - The localpart of the user to register. Must be covered + /// by the namespaces in the [`Registration`] in order to succeed. + pub async fn register(&self, localpart: impl AsRef) -> Result<()> { let request = assign!(RegistrationRequest::new(), { username: Some(localpart.as_ref()), login_type: Some(&LoginType::ApplicationService), }); - match self.client().register(request).await { + let client = self.client(None).await?; + match client.register(request).await { Ok(_) => (), Err(error) => match error { matrix_sdk::Error::Http(HttpError::UiaaError(FromHttpResponseError::Http( @@ -266,14 +317,14 @@ impl Appservice { /// Get the Appservice [registration] /// /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration - pub fn registration(&self) -> &Registration { + pub fn registration(&self) -> &AppserviceRegistration { &self.registration } /// Compare the given `hs_token` against `registration.hs_token` /// /// Returns `true` if the tokens match, `false` otherwise. - pub fn hs_token_matches(&self, hs_token: impl AsRef) -> bool { + pub fn compare_hs_token(&self, hs_token: impl AsRef) -> bool { self.registration.hs_token == hs_token.as_ref() } @@ -290,26 +341,6 @@ impl Appservice { Ok(false) } - /// Get the host and port from the registration URL - /// - /// If no port is found it falls back to scheme defaults: 80 for http and - /// 443 for https - pub fn get_host_and_port_from_registration(&self) -> Result<(Host, Port)> { - let uri = Uri::try_from(&self.registration.url)?; - - let host = uri.host().ok_or(Error::MissingRegistrationHost)?.to_owned(); - let port = match uri.port() { - Some(port) => Ok(port.as_u16()), - None => match uri.scheme_str() { - Some("http") => Ok(80), - Some("https") => Ok(443), - _ => Err(Error::MissingRegistrationPort), - }, - }?; - - Ok((host, port)) - } - /// Service to register on an Actix `App` #[cfg(feature = "actix")] #[cfg_attr(docs, doc(cfg(feature = "actix")))] diff --git a/matrix_sdk_appservice/tests/tests.rs b/matrix_sdk_appservice/tests/tests.rs index 30cdf2b7..61fe0ee4 100644 --- a/matrix_sdk_appservice/tests/tests.rs +++ b/matrix_sdk_appservice/tests/tests.rs @@ -76,7 +76,7 @@ async fn test_event_handler() -> Result<()> { } } - appservice.client().set_event_handler(Box::new(Example::new())).await; + appservice.set_event_handler(Box::new(Example::new())).await?; let event = serde_json::from_value::(member_json()).unwrap(); let event: Raw = AnyRoomEvent::State(event).into(); @@ -87,7 +87,7 @@ async fn test_event_handler() -> Result<()> { events, ); - appservice.client().receive_transaction(incoming).await?; + appservice.client(None).await?.receive_transaction(incoming).await?; Ok(()) } @@ -105,7 +105,7 @@ async fn test_transaction() -> Result<()> { events, ); - appservice.client().receive_transaction(incoming).await?; + appservice.client(None).await?.receive_transaction(incoming).await?; Ok(()) } @@ -116,7 +116,7 @@ async fn test_verify_hs_token() -> Result<()> { let registration = appservice.registration(); - assert!(appservice.hs_token_matches(®istration.hs_token)); + assert!(appservice.compare_hs_token(®istration.hs_token)); Ok(()) } diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index e3555ea4..d24a2f1c 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -20,7 +20,6 @@ use std::{ path::{Path, PathBuf}, result::Result as StdResult, sync::Arc, - time::SystemTime, }; #[cfg(feature = "encryption")] @@ -49,7 +48,7 @@ use matrix_sdk_common::{ instant::Instant, locks::RwLock, push::{Action, PushConditionRoomCtx, Ruleset}, - Raw, UInt, + MilliSecondsSinceUnixEpoch, Raw, UInt, }; #[cfg(feature = "encryption")] use matrix_sdk_crypto::{ @@ -100,8 +99,7 @@ pub struct AdditionalUnsignedData { pub fn hoist_and_deserialize_state_event( event: &Raw, ) -> StdResult { - let prev_content = - serde_json::from_str::(event.json().get())?.unsigned.prev_content; + let prev_content = event.deserialize_as::()?.unsigned.prev_content; let mut ev = event.deserialize()?; @@ -117,8 +115,7 @@ pub fn hoist_and_deserialize_state_event( fn hoist_member_event( event: &Raw>, ) -> StdResult, serde_json::Error> { - let prev_content = - serde_json::from_str::(event.json().get())?.unsigned.prev_content; + let prev_content = event.deserialize_as::()?.unsigned.prev_content; let mut e = event.deserialize()?; @@ -132,7 +129,8 @@ fn hoist_member_event( fn hoist_room_event_prev_content( event: &Raw, ) -> StdResult { - let prev_content = serde_json::from_str::(event.json().get()) + let prev_content = event + .deserialize_as::() .map(|more_unsigned| more_unsigned.unsigned) .map(|additional| additional.prev_content)? .and_then(|p| p.deserialize().ok()); @@ -515,7 +513,7 @@ impl BaseClient { event.event.clone(), false, room_id.clone(), - SystemTime::now(), + MilliSecondsSinceUnixEpoch::now(), ), ); } diff --git a/matrix_sdk_base/src/store/sled_store/mod.rs b/matrix_sdk_base/src/store/sled_store/mod.rs index 74ad0175..c60534bc 100644 --- a/matrix_sdk_base/src/store/sled_store/mod.rs +++ b/matrix_sdk_base/src/store/sled_store/mod.rs @@ -19,7 +19,7 @@ use std::{ convert::TryFrom, path::{Path, PathBuf}, sync::Arc, - time::SystemTime, + time::Instant, }; use futures::{ @@ -83,8 +83,9 @@ impl From for StoreError { } } +const ENCODE_SEPARATOR: u8 = 0xff; + trait EncodeKey { - const SEPARATOR: u8 = 0xff; fn encode(&self) -> Vec; } @@ -102,13 +103,13 @@ impl EncodeKey for &RoomId { impl EncodeKey for &str { fn encode(&self) -> Vec { - [self.as_bytes(), &[Self::SEPARATOR]].concat() + [self.as_bytes(), &[ENCODE_SEPARATOR]].concat() } } impl EncodeKey for (&str, &str) { fn encode(&self) -> Vec { - [self.0.as_bytes(), &[Self::SEPARATOR], self.1.as_bytes(), &[Self::SEPARATOR]].concat() + [self.0.as_bytes(), &[ENCODE_SEPARATOR], self.1.as_bytes(), &[ENCODE_SEPARATOR]].concat() } } @@ -116,11 +117,11 @@ impl EncodeKey for (&str, &str, &str) { fn encode(&self) -> Vec { [ self.0.as_bytes(), - &[Self::SEPARATOR], + &[ENCODE_SEPARATOR], self.1.as_bytes(), - &[Self::SEPARATOR], + &[ENCODE_SEPARATOR], self.2.as_bytes(), - &[Self::SEPARATOR], + &[ENCODE_SEPARATOR], ] .concat() } @@ -286,7 +287,7 @@ impl SledStore { } pub async fn save_changes(&self, changes: &StateChanges) -> Result<()> { - let now = SystemTime::now(); + let now = Instant::now(); let ret: Result<(), TransactionError> = ( &self.session, @@ -506,11 +507,22 @@ impl SledStore { .transpose()?) } - pub async fn get_user_ids(&self, room_id: &RoomId) -> impl Stream> { - stream::iter(self.members.scan_prefix(room_id.encode()).map(|u| { - UserId::try_from(String::from_utf8_lossy(&u?.1).to_string()) - .map_err(StoreError::Identifier) - })) + pub async fn get_user_ids_stream( + &self, + room_id: &RoomId, + ) -> impl Stream> { + let decode = |key: &[u8]| -> Result { + let mut iter = key.split(|c| c == &ENCODE_SEPARATOR); + // Our key is a the room id separated from the user id by a null + // byte, discard the first value of the split. + iter.next(); + + let user_id = iter.next().expect("User ids weren't properly encoded"); + + Ok(UserId::try_from(String::from_utf8_lossy(user_id).to_string())?) + }; + + stream::iter(self.members.scan_prefix(room_id.encode()).map(move |u| decode(&u?.0))) } pub async fn get_invited_user_ids( @@ -636,7 +648,7 @@ impl StateStore for SledStore { } async fn get_user_ids(&self, room_id: &RoomId) -> Result> { - self.get_user_ids(room_id).await.try_collect().await + self.get_user_ids_stream(room_id).await.try_collect().await } async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result> { @@ -681,7 +693,7 @@ impl StateStore for SledStore { #[cfg(test)] mod test { - use std::{convert::TryFrom, time::SystemTime}; + use std::convert::TryFrom; use matrix_sdk_common::{ events::{ @@ -692,13 +704,13 @@ mod test { AnySyncStateEvent, EventType, Unsigned, }, identifiers::{room_id, user_id, EventId, UserId}, - Raw, + MilliSecondsSinceUnixEpoch, Raw, }; use matrix_sdk_test::async_test; use serde_json::json; use super::{SledStore, StateChanges}; - use crate::deserialized_responses::MemberEvent; + use crate::{deserialized_responses::MemberEvent, StateStore}; fn user_id() -> UserId { user_id!("@example:localhost") @@ -721,19 +733,11 @@ mod test { } fn membership_event() -> MemberEvent { - let content = MemberEventContent { - avatar_url: None, - displayname: None, - is_direct: None, - third_party_invite: None, - membership: MembershipState::Join, - }; - MemberEvent { event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), - content, + content: MemberEventContent::new(MembershipState::Join), sender: user_id(), - origin_server_ts: SystemTime::now(), + origin_server_ts: MilliSecondsSinceUnixEpoch::now(), state_key: user_id(), prev_content: None, unsigned: Unsigned::default(), @@ -756,6 +760,9 @@ mod test { store.save_changes(&changes).await.unwrap(); assert!(store.get_member_event(&room_id, &user_id).await.unwrap().is_some()); + + let members = store.get_user_ids(&room_id).await.unwrap(); + assert!(!members.is_empty()) } #[async_test] diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 5206e11d..901d2724 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -20,9 +20,7 @@ serde = "1.0.122" async-trait = "0.1.42" [dependencies.ruma] -version = "0.0.3" -git = "https://github.com/ruma/ruma" -rev = "3bdead1cf207e3ab9c8fcbfc454c054c726ba6f5" +version = "0.1.0" features = ["client-api-c", "compat", "unstable-pre-spec"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/matrix_sdk_common/src/deserialized_responses.rs b/matrix_sdk_common/src/deserialized_responses.rs index 3acc4b4d..6582b0d2 100644 --- a/matrix_sdk_common/src/deserialized_responses.rs +++ b/matrix_sdk_common/src/deserialized_responses.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime}; +use std::{collections::BTreeMap, convert::TryFrom}; use ruma::{ api::client::r0::sync::sync_events::{ @@ -22,6 +22,7 @@ use super::{ SyncStateEvent, Unsigned, }, identifiers::{DeviceKeyAlgorithm, EventId, RoomId, UserId}, + MilliSecondsSinceUnixEpoch, }; /// A change in ambiguity of room members that an `m.room.member` event @@ -249,7 +250,7 @@ impl Timeline { pub struct MemberEvent { pub content: MemberEventContent, pub event_id: EventId, - pub origin_server_ts: SystemTime, + pub origin_server_ts: MilliSecondsSinceUnixEpoch, pub prev_content: Option, pub sender: UserId, pub state_key: UserId, diff --git a/matrix_sdk_common/src/lib.rs b/matrix_sdk_common/src/lib.rs index 5c91e21f..c45a082d 100644 --- a/matrix_sdk_common/src/lib.rs +++ b/matrix_sdk_common/src/lib.rs @@ -15,7 +15,7 @@ pub use ruma::{ }, assign, directory, encryption, events, identifiers, int, presence, push, receipt, serde::{CanonicalJsonValue, Raw}, - thirdparty, uint, Int, Outgoing, UInt, + thirdparty, uint, Int, MilliSecondsSinceUnixEpoch, Outgoing, SecondsSinceUnixEpoch, UInt, }; pub use uuid; diff --git a/matrix_sdk_crypto/src/file_encryption/attachments.rs b/matrix_sdk_crypto/src/file_encryption/attachments.rs index 8b83aa08..81c3cf23 100644 --- a/matrix_sdk_crypto/src/file_encryption/attachments.rs +++ b/matrix_sdk_crypto/src/file_encryption/attachments.rs @@ -23,7 +23,7 @@ use aes_ctr::{ }; use base64::DecodeError; use getrandom::getrandom; -use matrix_sdk_common::events::room::JsonWebKey; +use matrix_sdk_common::events::room::{JsonWebKey, JsonWebKeyInit}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -201,13 +201,13 @@ impl<'a, R: Read + 'a> AttachmentEncryptor<'a, R> { // initialized. getrandom(&mut iv[0..8]).expect("Can't generate randomness"); - let web_key = JsonWebKey { + let web_key = JsonWebKey::from(JsonWebKeyInit { kty: "oct".to_owned(), key_ops: vec!["encrypt".to_owned(), "decrypt".to_owned()], alg: "A256CTR".to_owned(), k: encode_url_safe(&*key), ext: true, - }; + }); let encoded_iv = encode(&*iv); let aes = Aes256Ctr::new_var(&*key, &*iv).expect("Cannot create AES encryption object."); diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index 67a3a494..f1dbb43a 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -151,12 +151,12 @@ pub struct OutgoingKeyRequest { impl OutgoingKeyRequest { fn to_request(&self, own_device_id: &DeviceId) -> Result { - let content = RoomKeyRequestToDeviceEventContent { - action: Action::Request, - request_id: self.request_id.to_string(), - requesting_device_id: own_device_id.to_owned(), - body: Some(self.info.clone()), - }; + let content = RoomKeyRequestToDeviceEventContent::new( + Action::Request, + Some(self.info.clone()), + own_device_id.to_owned(), + self.request_id.to_string(), + ); wrap_key_request_content(self.request_recipient.clone(), self.request_id, &content) } @@ -165,12 +165,12 @@ impl OutgoingKeyRequest { &self, own_device_id: &DeviceId, ) -> Result { - let content = RoomKeyRequestToDeviceEventContent { - action: Action::CancelRequest, - request_id: self.request_id.to_string(), - requesting_device_id: own_device_id.to_owned(), - body: None, - }; + let content = RoomKeyRequestToDeviceEventContent::new( + Action::CancelRequest, + None, + own_device_id.to_owned(), + self.request_id.to_string(), + ); let id = Uuid::new_v4(); wrap_key_request_content(self.request_recipient.clone(), id, &content) @@ -584,12 +584,12 @@ impl KeyRequestMachine { sender_key: &str, session_id: &str, ) -> Result<(Option, OutgoingRequest), CryptoStoreError> { - let key_info = RequestedKeyInfo { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - room_id: room_id.to_owned(), - sender_key: sender_key.to_owned(), - session_id: session_id.to_owned(), - }; + let key_info = RequestedKeyInfo::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + room_id.to_owned(), + sender_key.to_owned(), + session_id.to_owned(), + ); let request = self.store.get_key_request_by_info(&key_info).await?; @@ -644,12 +644,12 @@ impl KeyRequestMachine { sender_key: &str, session_id: &str, ) -> Result<(), CryptoStoreError> { - let key_info = RequestedKeyInfo { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - room_id: room_id.to_owned(), - sender_key: sender_key.to_owned(), - session_id: session_id.to_owned(), - }; + let key_info = RequestedKeyInfo::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + room_id.to_owned(), + sender_key.to_owned(), + session_id.to_owned(), + ); if self.should_request_key(&key_info).await? { self.request_key_helper(key_info).await?; @@ -675,12 +675,12 @@ impl KeyRequestMachine { &self, content: &ForwardedRoomKeyToDeviceEventContent, ) -> Result, CryptoStoreError> { - let info = RequestedKeyInfo { - algorithm: content.algorithm.clone(), - room_id: content.room_id.clone(), - sender_key: content.sender_key.clone(), - session_id: content.session_id.clone(), - }; + let info = RequestedKeyInfo::new( + content.algorithm.clone(), + content.room_id.clone(), + content.sender_key.clone(), + content.session_id.clone(), + ); self.store.get_key_request_by_info(&info).await } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index e64b15ab..0511d5a4 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -1216,7 +1216,6 @@ pub(crate) mod test { collections::BTreeMap, convert::{TryFrom, TryInto}, sync::Arc, - time::SystemTime, }; use http::Response; @@ -1233,7 +1232,7 @@ pub(crate) mod test { identifiers::{ event_id, room_id, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId, }, - IncomingResponse, Raw, + IncomingResponse, MilliSecondsSinceUnixEpoch, Raw, }; use matrix_sdk_test::test_json; use serde_json::json; @@ -1680,7 +1679,7 @@ pub(crate) mod test { let event = SyncMessageEvent { event_id: event_id!("$xxxxx:example.org"), - origin_server_ts: SystemTime::now(), + origin_server_ts: MilliSecondsSinceUnixEpoch::now(), sender: alice.user_id().clone(), content: encrypted_content, unsigned: Unsigned::default(), diff --git a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs index cb81ad73..957fbed8 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/inbound.rs @@ -12,12 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ - collections::BTreeMap, - convert::{TryFrom, TryInto}, - fmt, mem, - sync::Arc, -}; +use std::{collections::BTreeMap, convert::TryFrom, fmt, mem, sync::Arc}; use matrix_sdk_common::{ events::{ @@ -310,13 +305,7 @@ impl InboundGroupSession { let mut decrypted_value = serde_json::from_str::(&plaintext)?; let decrypted_object = decrypted_value.as_object_mut().ok_or(EventError::NotAnObject)?; - // TODO better number conversion here. - let server_ts = event - .origin_server_ts - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .unwrap_or_default() - .as_millis(); - let server_ts: i64 = server_ts.try_into().unwrap_or_default(); + let server_ts: i64 = event.origin_server_ts.0.into(); decrypted_object.insert("sender".to_owned(), event.sender.to_string().into()); decrypted_object.insert("event_id".to_owned(), event.event_id.to_string().into()); diff --git a/matrix_sdk_crypto/src/store/sled.rs b/matrix_sdk_crypto/src/store/sled.rs index 3bca87ab..2a40b083 100644 --- a/matrix_sdk_crypto/src/store/sled.rs +++ b/matrix_sdk_crypto/src/store/sled.rs @@ -1205,12 +1205,12 @@ mod test { let (account, store, _dir) = get_loaded_store().await; let id = Uuid::new_v4(); - let info = RequestedKeyInfo { - algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, - room_id: room_id!("!test:localhost"), - sender_key: "test_sender_key".to_string(), - session_id: "test_session_id".to_string(), - }; + let info = RequestedKeyInfo::new( + EventEncryptionAlgorithm::MegolmV1AesSha2, + room_id!("!test:localhost"), + "test_sender_key".to_string(), + "test_session_id".to_string(), + ); let request = OutgoingKeyRequest { request_recipient: account.user_id().to_owned(), diff --git a/matrix_sdk_crypto/src/verification/requests.rs b/matrix_sdk_crypto/src/verification/requests.rs index 842bbe74..ab99cd4c 100644 --- a/matrix_sdk_crypto/src/verification/requests.rs +++ b/matrix_sdk_crypto/src/verification/requests.rs @@ -184,17 +184,17 @@ impl VerificationRequest { own_device_id: &DeviceId, other_user_id: &UserId, ) -> KeyVerificationRequestEventContent { - KeyVerificationRequestEventContent { - body: format!( + KeyVerificationRequestEventContent::new( + format!( "{} is requesting to verify your key, but your client does not \ support in-chat key verification. You will need to use legacy \ key verification to verify keys.", own_user_id ), - methods: SUPPORTED_METHODS.to_vec(), - from_device: own_device_id.into(), - to: other_user_id.to_owned(), - } + SUPPORTED_METHODS.to_vec(), + own_device_id.into(), + other_user_id.to_owned(), + ) } /// The id of the other user that is participating in this verification @@ -515,21 +515,17 @@ impl RequestState { }; let content = match self.state.flow_id { - FlowId::ToDevice(i) => { - AnyToDeviceEventContent::KeyVerificationReady(ReadyToDeviceEventContent { - from_device: self.own_device_id, - methods: self.state.methods, - transaction_id: i, - }) - .into() - } + FlowId::ToDevice(i) => AnyToDeviceEventContent::KeyVerificationReady( + ReadyToDeviceEventContent::new(self.own_device_id, self.state.methods, i), + ) + .into(), FlowId::InRoom(r, e) => ( r, - AnyMessageEventContent::KeyVerificationReady(ReadyEventContent { - from_device: self.own_device_id, - methods: self.state.methods, - relation: Relation { event_id: e }, - }), + AnyMessageEventContent::KeyVerificationReady(ReadyEventContent::new( + self.own_device_id, + self.state.methods, + Relation::new(e), + )), ) .into(), }; @@ -608,11 +604,12 @@ struct Passive { #[cfg(test)] mod test { - use std::{convert::TryFrom, time::SystemTime}; + use std::convert::TryFrom; use matrix_sdk_common::{ events::{SyncMessageEvent, Unsigned}, identifiers::{event_id, room_id, DeviceIdBox, UserId}, + MilliSecondsSinceUnixEpoch, }; use matrix_sdk_test::async_test; @@ -738,7 +735,7 @@ mod test { content: c, event_id: event_id.clone(), sender: bob_id(), - origin_server_ts: SystemTime::now(), + origin_server_ts: MilliSecondsSinceUnixEpoch::now(), unsigned: Unsigned::default(), } } else { diff --git a/matrix_sdk_crypto/src/verification/sas/helpers.rs b/matrix_sdk_crypto/src/verification/sas/helpers.rs index 3aa3ec1c..b0092383 100644 --- a/matrix_sdk_crypto/src/verification/sas/helpers.rs +++ b/matrix_sdk_crypto/src/verification/sas/helpers.rs @@ -315,12 +315,9 @@ pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &FlowId) -> MacConte .expect("Can't calculate SAS MAC"); match flow_id { - FlowId::ToDevice(s) => { - MacToDeviceEventContent { transaction_id: s.to_string(), keys, mac }.into() - } + FlowId::ToDevice(s) => MacToDeviceEventContent::new(s.to_string(), mac, keys).into(), FlowId::InRoom(r, e) => { - (r.clone(), MacEventContent { mac, keys, relation: Relation { event_id: e.clone() } }) - .into() + (r.clone(), MacEventContent::new(mac, keys, Relation::new(e.clone()))).into() } } } diff --git a/matrix_sdk_crypto/src/verification/sas/sas_state.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs index 18af104a..ec8ea413 100644 --- a/matrix_sdk_crypto/src/verification/sas/sas_state.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -23,13 +23,13 @@ use matrix_sdk_common::{ events::key::verification::{ accept::{ AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent, - MSasV1Content as AcceptV1Content, MSasV1ContentInit as AcceptV1ContentInit, + SasV1Content as AcceptV1Content, SasV1ContentInit as AcceptV1ContentInit, }, cancel::{CancelCode, CancelEventContent, CancelToDeviceEventContent}, done::DoneEventContent, key::{KeyEventContent, KeyToDeviceEventContent}, start::{ - MSasV1Content, MSasV1ContentInit, StartEventContent, StartMethod, + SasV1Content, SasV1ContentInit, StartEventContent, StartMethod, StartToDeviceEventContent, }, HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, Relation, @@ -105,10 +105,10 @@ impl TryFrom for AcceptedProtocols { } } -impl TryFrom<&MSasV1Content> for AcceptedProtocols { +impl TryFrom<&SasV1Content> for AcceptedProtocols { type Error = CancelCode; - fn try_from(method_content: &MSasV1Content) -> Result { + fn try_from(method_content: &SasV1Content) -> Result { if !method_content .key_agreement_protocols .contains(&KeyAgreementProtocol::Curve25519HkdfSha256) @@ -212,7 +212,7 @@ impl std::fmt::Debug for SasState { /// The initial SAS state. #[derive(Clone, Debug)] pub struct Created { - protocol_definitions: MSasV1ContentInit, + protocol_definitions: SasV1ContentInit, } /// The initial SAS state if the other side started the SAS verification. @@ -403,7 +403,7 @@ impl SasState { last_event_time: Arc::new(Instant::now()), state: Arc::new(Created { - protocol_definitions: MSasV1ContentInit { + protocol_definitions: SasV1ContentInit { short_authentication_string: STRINGS.to_vec(), key_agreement_protocols: KEY_AGREEMENT_PROTOCOLS.to_vec(), message_authentication_codes: MACS.to_vec(), @@ -415,24 +415,24 @@ impl SasState { pub fn as_content(&self) -> StartContent { match self.verification_flow_id.as_ref() { - FlowId::ToDevice(s) => StartContent::ToDevice(StartToDeviceEventContent { - transaction_id: s.to_string(), - from_device: self.device_id().into(), - method: StartMethod::MSasV1( - MSasV1Content::new(self.state.protocol_definitions.clone()) + FlowId::ToDevice(s) => StartContent::ToDevice(StartToDeviceEventContent::new( + self.device_id().into(), + s.to_string(), + StartMethod::SasV1( + SasV1Content::new(self.state.protocol_definitions.clone()) .expect("Invalid initial protocol definitions."), ), - }), + )), FlowId::InRoom(r, e) => StartContent::Room( r.clone(), - StartEventContent { - from_device: self.device_id().into(), - method: StartMethod::MSasV1( - MSasV1Content::new(self.state.protocol_definitions.clone()) + StartEventContent::new( + self.device_id().into(), + StartMethod::SasV1( + SasV1Content::new(self.state.protocol_definitions.clone()) .expect("Invalid initial protocol definitions."), ), - relation: Relation { event_id: e.clone() }, - }, + Relation::new(e.clone()), + ), ), } } @@ -522,7 +522,7 @@ impl SasState { state: Arc::new(Canceled::new(CancelCode::UnknownMethod)), }; - if let StartMethod::MSasV1(method_content) = content.method() { + if let StartMethod::SasV1(method_content) = content.method() { let sas = OlmSas::new(); let pubkey = sas.public_key(); @@ -589,14 +589,10 @@ impl SasState { ); match self.verification_flow_id.as_ref() { - FlowId::ToDevice(s) => { - AcceptToDeviceEventContent { transaction_id: s.to_string(), method }.into() + FlowId::ToDevice(s) => AcceptToDeviceEventContent::new(s.to_string(), method).into(), + FlowId::InRoom(r, e) => { + (r.clone(), AcceptEventContent::new(method, Relation::new(e.clone()))).into() } - FlowId::InRoom(r, e) => ( - r.clone(), - AcceptEventContent { method, relation: Relation { event_id: e.clone() } }, - ) - .into(), } } @@ -701,10 +697,10 @@ impl SasState { .into(), FlowId::InRoom(r, e) => ( r.clone(), - KeyEventContent { - key: self.inner.lock().unwrap().public_key(), - relation: Relation { event_id: e.clone() }, - }, + KeyEventContent::new( + self.inner.lock().unwrap().public_key(), + Relation::new(e.clone()), + ), ) .into(), } @@ -725,10 +721,10 @@ impl SasState { .into(), FlowId::InRoom(r, e) => ( r.clone(), - KeyEventContent { - key: self.inner.lock().unwrap().public_key(), - relation: Relation { event_id: e.clone() }, - }, + KeyEventContent::new( + self.inner.lock().unwrap().public_key(), + Relation::new(e.clone()), + ), ) .into(), } @@ -1024,7 +1020,7 @@ impl SasState { unreachable!("The done content isn't supported yet for to-device verifications") } FlowId::InRoom(r, e) => { - (r.clone(), DoneEventContent { relation: Relation { event_id: e.clone() } }).into() + (r.clone(), DoneEventContent::new(Relation::new(e.clone()))).into() } } } @@ -1076,7 +1072,7 @@ impl SasState { unreachable!("The done content isn't supported yet for to-device verifications") } FlowId::InRoom(r, e) => { - (r.clone(), DoneEventContent { relation: Relation { event_id: e.clone() } }).into() + (r.clone(), DoneEventContent::new(Relation::new(e.clone()))).into() } } } @@ -1120,20 +1116,20 @@ impl Canceled { impl SasState { pub fn as_content(&self) -> CancelContent { match self.verification_flow_id.as_ref() { - FlowId::ToDevice(s) => CancelToDeviceEventContent { - transaction_id: s.clone(), - reason: self.state.reason.to_string(), - code: self.state.cancel_code.clone(), - } + FlowId::ToDevice(s) => CancelToDeviceEventContent::new( + s.clone(), + self.state.reason.to_string(), + self.state.cancel_code.clone(), + ) .into(), FlowId::InRoom(r, e) => ( r.clone(), - CancelEventContent { - reason: self.state.reason.to_string(), - code: self.state.cancel_code.clone(), - relation: Relation { event_id: e.clone() }, - }, + CancelEventContent::new( + self.state.reason.to_string(), + self.state.cancel_code.clone(), + Relation::new(e.clone()), + ), ) .into(), } @@ -1360,7 +1356,7 @@ mod test { }; match method { - StartMethod::MSasV1(ref mut c) => { + StartMethod::SasV1(ref mut c) => { c.message_authentication_codes = vec![]; } _ => panic!("Unknown SAS start method"),