From fb4b3474331a99c0e98b38ea42568473ba18417c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 12:17:15 +0100 Subject: [PATCH 01/28] base: Rename RoomType to RoomState --- crates/matrix-sdk-base/Changelog.md | 4 +- crates/matrix-sdk-base/src/client.rs | 26 ++++----- crates/matrix-sdk-base/src/lib.rs | 2 +- crates/matrix-sdk-base/src/rooms/mod.rs | 2 +- crates/matrix-sdk-base/src/rooms/normal.rs | 56 +++++++++---------- crates/matrix-sdk-base/src/sliding_sync.rs | 4 +- .../src/store/integration_tests.rs | 10 ++-- crates/matrix-sdk-base/src/store/mod.rs | 16 +++--- crates/matrix-sdk/src/client/mod.rs | 4 +- crates/matrix-sdk/src/lib.rs | 2 +- crates/matrix-sdk/src/room/common.rs | 4 +- crates/matrix-sdk/src/room/invited.rs | 14 ++--- crates/matrix-sdk/src/room/joined.rs | 16 +++--- crates/matrix-sdk/src/room/left.rs | 14 ++--- crates/matrix-sdk/src/room/mod.rs | 34 +++++------ .../src/tests/repeated_join.rs | 4 +- 16 files changed, 107 insertions(+), 105 deletions(-) diff --git a/crates/matrix-sdk-base/Changelog.md b/crates/matrix-sdk-base/Changelog.md index f8e6777b982..b10574bf534 100644 --- a/crates/matrix-sdk-base/Changelog.md +++ b/crates/matrix-sdk-base/Changelog.md @@ -1,6 +1,8 @@ # Changelog -All notable changes to this crate will be documented in this file. +## unreleased + +- Rename `RoomType` to `RoomState` ## 0.5.1 diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 2fdf4735604..8af784eb4f9 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -60,7 +60,7 @@ use crate::error::Error; use crate::{ deserialized_responses::{AmbiguityChanges, MembersResponse, SyncTimelineEvent}, error::Result, - rooms::{Room, RoomInfo, RoomType}, + rooms::{Room, RoomInfo, RoomState}, store::{ ambiguity_map::AmbiguityCache, DynStateStore, Result as StoreResult, StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt, Store, StoreConfig, @@ -167,8 +167,8 @@ impl BaseClient { /// Lookup the Room for the given RoomId, or create one, if it didn't exist /// yet in the store - pub async fn get_or_create_room(&self, room_id: &RoomId, room_type: RoomType) -> Room { - self.store.get_or_create_room(room_id, room_type).await + pub async fn get_or_create_room(&self, room_id: &RoomId, room_state: RoomState) -> Room { + self.store.get_or_create_room(room_id, room_state).await } /// Get all the rooms this client knows about. @@ -623,8 +623,8 @@ impl BaseClient { /// /// Update the internal and cached state accordingly. Return the final Room. pub async fn room_joined(&self, room_id: &RoomId) -> Result { - let room = self.store.get_or_create_room(room_id, RoomType::Joined).await; - if room.room_type() != RoomType::Joined { + let room = self.store.get_or_create_room(room_id, RoomState::Joined).await; + if room.state() != RoomState::Joined { let _sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); @@ -644,8 +644,8 @@ impl BaseClient { /// /// Update the internal and cached state accordingly. Return the final Room. pub async fn room_left(&self, room_id: &RoomId) -> Result { - let room = self.store.get_or_create_room(room_id, RoomType::Left).await; - if room.room_type() != RoomType::Left { + let room = self.store.get_or_create_room(room_id, RoomState::Left).await; + if room.state() != RoomState::Left { let _sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); @@ -718,7 +718,7 @@ impl BaseClient { let mut new_rooms = Rooms::default(); for (room_id, new_info) in rooms.join { - let room = self.store.get_or_create_room(&room_id, RoomType::Joined).await; + let room = self.store.get_or_create_room(&room_id, RoomState::Joined).await; let mut room_info = room.clone_info(); room_info.mark_as_joined(); @@ -802,7 +802,7 @@ impl BaseClient { } for (room_id, new_info) in rooms.leave { - let room = self.store.get_or_create_room(&room_id, RoomType::Left).await; + let room = self.store.get_or_create_room(&room_id, RoomState::Left).await; let mut room_info = room.clone_info(); room_info.mark_as_left(); room_info.mark_state_partially_synced(); @@ -1254,7 +1254,7 @@ mod tests { use serde_json::json; use super::BaseClient; - use crate::{DisplayName, RoomType, SessionMeta}; + use crate::{DisplayName, RoomState, SessionMeta}; #[async_test] async fn invite_after_leaving() { @@ -1288,7 +1288,7 @@ mod tests { )) .build_sync_response(); client.receive_sync_response(response).await.unwrap(); - assert_eq!(client.get_room(room_id).unwrap().room_type(), RoomType::Left); + assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left); let response = ev_builder .add_invited_room(InvitedRoomBuilder::new(room_id).add_state_event( @@ -1306,7 +1306,7 @@ mod tests { )) .build_sync_response(); client.receive_sync_response(response).await.unwrap(); - assert_eq!(client.get_room(room_id).unwrap().room_type(), RoomType::Invited); + assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited); } #[async_test] @@ -1397,7 +1397,7 @@ mod tests { client.receive_sync_response(response).await.unwrap(); let room = client.get_room(room_id).expect("Room not found"); - assert_eq!(room.room_type(), RoomType::Invited); + assert_eq!(room.state(), RoomState::Invited); assert_eq!( room.display_name().await.expect("fetching display name failed"), DisplayName::Calculated("Kyra".to_owned()) diff --git a/crates/matrix-sdk-base/src/lib.rs b/crates/matrix-sdk-base/src/lib.rs index d411e5663b1..c69e4867f23 100644 --- a/crates/matrix-sdk-base/src/lib.rs +++ b/crates/matrix-sdk-base/src/lib.rs @@ -42,7 +42,7 @@ pub use http; #[cfg(feature = "e2e-encryption")] pub use matrix_sdk_crypto as crypto; pub use once_cell; -pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomType}; +pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomState}; pub use store::{StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError}; pub use utils::{ MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent, diff --git a/crates/matrix-sdk-base/src/rooms/mod.rs b/crates/matrix-sdk-base/src/rooms/mod.rs index 7ef982238ef..f414fd20ee4 100644 --- a/crates/matrix-sdk-base/src/rooms/mod.rs +++ b/crates/matrix-sdk-base/src/rooms/mod.rs @@ -4,7 +4,7 @@ mod normal; use std::{collections::HashSet, fmt}; pub use members::RoomMember; -pub use normal::{Room, RoomInfo, RoomType}; +pub use normal::{Room, RoomInfo, RoomState}; use ruma::{ assign, events::{ diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index f9e9314e8ac..1f6b10d6520 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -31,7 +31,7 @@ use ruma::{ AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, RoomAccountDataEventType, }, - room::RoomType as CreateRoomType, + room::RoomType, EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId, }; @@ -71,7 +71,7 @@ pub struct RoomSummary { /// Enum keeping track in which state the room is, e.g. if our own user is /// joined, invited, or has left the room. #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum RoomType { +pub enum RoomState { /// The room is in a joined state. Joined, /// The room is in a left state. @@ -85,9 +85,9 @@ impl Room { own_user_id: &UserId, store: Arc, room_id: &RoomId, - room_type: RoomType, + room_state: RoomState, ) -> Self { - let room_info = RoomInfo::new(room_id, room_type); + let room_info = RoomInfo::new(room_id, room_state); Self::restore(own_user_id, store, room_info) } @@ -114,14 +114,14 @@ impl Room { &self.own_user_id } - /// Get the type of the room. - pub fn room_type(&self) -> RoomType { - self.inner.read().unwrap().room_type + /// Get the state of the room. + pub fn state(&self) -> RoomState { + self.inner.read().unwrap().room_state } - /// Whether this room's [`RoomType`](CreateRoomType) is `m.space`. + /// Whether this room's [`RoomType`] is `m.space`. pub fn is_space(&self) -> bool { - self.inner.read().unwrap().room_type().map_or(false, |t| *t == CreateRoomType::Space) + self.inner.read().unwrap().room_type().map_or(false, |t| *t == RoomType::Space) } /// Get the unread notification counts. @@ -381,13 +381,13 @@ impl Room { members? }; - let (joined, invited) = match self.room_type() { - RoomType::Invited => { + let (joined, invited) = match self.state() { + RoomState::Invited => { // when we were invited we don't have a proper summary, we have to do best // guessing (members.len() as u64, 1u64) } - RoomType::Joined if summary.joined_member_count == 0 => { + RoomState::Joined if summary.joined_member_count == 0 => { // joined but the summary is not completed yet ( (members.len() as u64) + 1, // we've taken ourselves out of the count @@ -514,8 +514,8 @@ impl Room { pub struct RoomInfo { /// The unique room id of the room. pub(crate) room_id: Arc, - /// The type of the room. - room_type: RoomType, + /// The state of the room. + room_state: RoomState, /// The unread notifications counts. notification_counts: UnreadNotificationsCount, /// The summary of this room. @@ -572,10 +572,10 @@ fn encryption_state_default() -> bool { impl RoomInfo { #[doc(hidden)] // used by store tests, otherwise it would be pub(crate) - pub fn new(room_id: &RoomId, room_type: RoomType) -> Self { + pub fn new(room_id: &RoomId, room_state: RoomState) -> Self { Self { room_id: room_id.into(), - room_type, + room_state, notification_counts: Default::default(), summary: Default::default(), members_synced: false, @@ -588,17 +588,17 @@ impl RoomInfo { /// Mark this Room as joined. pub fn mark_as_joined(&mut self) { - self.room_type = RoomType::Joined; + self.room_state = RoomState::Joined; } /// Mark this Room as left. pub fn mark_as_left(&mut self) { - self.room_type = RoomType::Left; + self.room_state = RoomState::Left; } /// Mark this Room as invited. pub fn mark_as_invited(&mut self) { - self.room_type = RoomType::Invited; + self.room_state = RoomState::Invited; } /// Mark this Room as having all the members synced. @@ -743,7 +743,7 @@ impl RoomInfo { } /// Get the room type of this room. - pub fn room_type(&self) -> Option<&CreateRoomType> { + pub fn room_type(&self) -> Option<&RoomType> { self.base_info.create.as_ref()?.as_original()?.content.room_type.as_ref() } @@ -815,7 +815,7 @@ mod test { MinimalStateEvent, OriginalMinimalStateEvent, }; - fn make_room(room_type: RoomType) -> (Arc, Room) { + fn make_room(room_type: RoomState) -> (Arc, Room) { let store = Arc::new(MemoryStore::new()); let user_id = user_id!("@me:example.org"); let room_id = room_id!("!test:localhost"); @@ -853,7 +853,7 @@ mod test { #[async_test] async fn test_display_name_default() { - let (_, room) = make_room(RoomType::Joined); + let (_, room) = make_room(RoomState::Joined); assert_eq!(room.display_name().await.unwrap(), DisplayName::Empty); let canonical_alias_event = MinimalStateEvent::Original(OriginalMinimalStateEvent { @@ -876,7 +876,7 @@ mod test { room.inner.write().unwrap().base_info.name = Some(name_event.clone()); assert_eq!(room.display_name().await.unwrap(), DisplayName::Named("Test Room".to_owned())); - let (_, room) = make_room(RoomType::Invited); + let (_, room) = make_room(RoomState::Invited); assert_eq!(room.display_name().await.unwrap(), DisplayName::Empty); // has precedence @@ -890,7 +890,7 @@ mod test { #[async_test] async fn test_display_name_dm_invited() { - let (store, room) = make_room(RoomType::Invited); + let (store, room) = make_room(RoomState::Invited); let room_id = room_id!("!test:localhost"); let matthew = user_id!("@matthew:example.org"); let me = user_id!("@me:example.org"); @@ -916,7 +916,7 @@ mod test { #[async_test] async fn test_display_name_dm_invited_no_heroes() { - let (store, room) = make_room(RoomType::Invited); + let (store, room) = make_room(RoomState::Invited); let room_id = room_id!("!test:localhost"); let matthew = user_id!("@matthew:example.org"); let me = user_id!("@me:example.org"); @@ -938,7 +938,7 @@ mod test { #[async_test] async fn test_display_name_dm_joined() { - let (store, room) = make_room(RoomType::Joined); + let (store, room) = make_room(RoomState::Joined); let room_id = room_id!("!test:localhost"); let matthew = user_id!("@matthew:example.org"); let me = user_id!("@me:example.org"); @@ -969,7 +969,7 @@ mod test { #[async_test] async fn test_display_name_dm_joined_no_heroes() { - let (store, room) = make_room(RoomType::Joined); + let (store, room) = make_room(RoomState::Joined); let room_id = room_id!("!test:localhost"); let matthew = user_id!("@matthew:example.org"); let me = user_id!("@me:example.org"); @@ -995,7 +995,7 @@ mod test { #[async_test] async fn test_display_name_dm_alone() { - let (store, room) = make_room(RoomType::Joined); + let (store, room) = make_room(RoomState::Joined); let room_id = room_id!("!test:localhost"); let matthew = user_id!("@matthew:example.org"); let me = user_id!("@me:example.org"); diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index 23ede4ef4c8..0b1cd684705 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -17,7 +17,7 @@ use super::BaseClient; use crate::{ deserialized_responses::AmbiguityChanges, error::Result, - rooms::RoomType, + rooms::RoomState, store::{ambiguity_map::AmbiguityCache, StateChanges}, sync::{JoinedRoom, Rooms, SyncResponse}, }; @@ -138,7 +138,7 @@ impl BaseClient { v3::InvitedRoom::from(v3::InviteState::from(invite_states.clone())), ); } else { - let room = store.get_or_create_room(room_id, RoomType::Joined).await; + let room = store.get_or_create_room(room_id, RoomState::Joined).await; let mut room_info = room.clone_info(); room_info.mark_as_joined(); // FIXME: this might not be accurate room_info.mark_state_partially_synced(); diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index 982ed149ab6..05fe6e0490b 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -35,7 +35,7 @@ use crate::{ deserialized_responses::MemberEvent, media::{MediaFormat, MediaRequest, MediaThumbnailSize}, store::{Result, StateStoreExt}, - RoomInfo, RoomType, StateChanges, StateStoreDataKey, StateStoreDataValue, + RoomInfo, RoomState, StateChanges, StateStoreDataKey, StateStoreDataValue, }; /// `StateStore` integration tests. @@ -101,7 +101,7 @@ impl StateStoreIntegrationTests for DynStateStore { let pushrules_event = pushrules_raw.deserialize().unwrap(); changes.add_account_data(pushrules_event, pushrules_raw); - let mut room = RoomInfo::new(room_id, RoomType::Joined); + let mut room = RoomInfo::new(room_id, RoomState::Joined); room.mark_as_left(); let tag_json: &JsonValue = &test_json::TAG; @@ -168,7 +168,7 @@ impl StateStoreIntegrationTests for DynStateStore { changes.members.insert(room_id.to_owned(), room_members); changes.add_room(room); - let mut stripped_room = RoomInfo::new(stripped_room_id, RoomType::Invited); + let mut stripped_room = RoomInfo::new(stripped_room_id, RoomState::Invited); let stripped_name_json: &JsonValue = &test_json::NAME_STRIPPED; let stripped_name_raw = @@ -764,7 +764,7 @@ impl StateStoreIntegrationTests for DynStateStore { .entry(room_id.to_owned()) .or_default() .insert(user_id.to_owned(), membership_event()); - changes.add_room(RoomInfo::new(room_id, RoomType::Left)); + changes.add_room(RoomInfo::new(room_id, RoomState::Left)); self.save_changes(&changes).await.unwrap(); let member_event = @@ -778,7 +778,7 @@ impl StateStoreIntegrationTests for DynStateStore { let mut changes = StateChanges::default(); changes.add_stripped_member(room_id, user_id, custom_stripped_membership_event(user_id)); - changes.add_stripped_room(RoomInfo::new(room_id, RoomType::Invited)); + changes.add_stripped_room(RoomInfo::new(room_id, RoomState::Invited)); self.save_changes(&changes).await.unwrap(); let member_event = diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 11dc6b5121b..35a0cdb4114 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -63,7 +63,7 @@ use ruma::{ pub type BoxStream = Pin + Send>>; use crate::{ - rooms::{RoomInfo, RoomType}, + rooms::{RoomInfo, RoomState}, MinimalRoomMemberEvent, Room, Session, SessionMeta, SessionTokens, }; @@ -236,10 +236,10 @@ impl Store { pub fn get_room(&self, room_id: &RoomId) -> Option { self.rooms .get(room_id) - .and_then(|r| match r.room_type() { - RoomType::Joined => Some(r.clone()), - RoomType::Left => Some(r.clone()), - RoomType::Invited => self.get_stripped_room(room_id), + .and_then(|r| match r.state() { + RoomState::Joined => Some(r.clone()), + RoomState::Left => Some(r.clone()), + RoomState::Invited => self.get_stripped_room(room_id), }) .or_else(|| self.get_stripped_room(room_id)) } @@ -265,14 +265,14 @@ impl Store { self.stripped_rooms .entry(room_id.to_owned()) - .or_insert_with(|| Room::new(user_id, self.inner.clone(), room_id, RoomType::Invited)) + .or_insert_with(|| Room::new(user_id, self.inner.clone(), room_id, RoomState::Invited)) .clone() } /// Lookup the Room for the given RoomId, or create one, if it didn't exist /// yet in the store - pub async fn get_or_create_room(&self, room_id: &RoomId, room_type: RoomType) -> Room { - if room_type == RoomType::Invited { + pub async fn get_or_create_room(&self, room_id: &RoomId, room_type: RoomState) -> Room { + if room_type == RoomState::Invited { return self.get_or_create_stripped_room(room_id).await; } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index d1934173cf4..1f3fb62a108 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -28,7 +28,7 @@ use dashmap::DashMap; use futures_core::Stream; use futures_util::StreamExt; use matrix_sdk_base::{ - store::DynStateStore, BaseClient, RoomType, SendOutsideWasm, Session, SessionMeta, + store::DynStateStore, BaseClient, RoomState, SendOutsideWasm, Session, SessionMeta, SessionTokens, SyncOutsideWasm, }; use matrix_sdk_common::{ @@ -1684,7 +1684,7 @@ impl Client { let response = self.send(request, None).await?; let base_room = - self.base_client().get_or_create_room(&response.room_id, RoomType::Joined).await; + self.base_client().get_or_create_room(&response.room_id, RoomState::Joined).await; Ok(room::Joined::new(self, base_room).unwrap()) } diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index fa6776d3245..12f92363294 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -20,7 +20,7 @@ pub use async_trait::async_trait; pub use bytes; pub use matrix_sdk_base::{ deserialized_responses, DisplayName, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, - RoomType, Session, StateChanges, StoreError, + RoomState, Session, StateChanges, StoreError, }; pub use matrix_sdk_common::*; pub use reqwest; diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 035fd62f2b7..60c07096374 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -51,7 +51,7 @@ use super::Joined; use crate::{ event_handler::{EventHandler, EventHandlerHandle, SyncEvent}, media::{MediaFormat, MediaRequest}, - room::{Left, RoomMember, RoomType}, + room::{Left, RoomMember, RoomState}, BaseRoom, Client, Error, HttpError, HttpResult, Result, }; @@ -404,7 +404,7 @@ impl Common { } fn are_events_visible(&self) -> bool { - if let RoomType::Invited = self.inner.room_type() { + if let RoomState::Invited = self.inner.state() { return matches!( self.inner.history_visibility(), HistoryVisibility::WorldReadable | HistoryVisibility::Invited diff --git a/crates/matrix-sdk/src/room/invited.rs b/crates/matrix-sdk/src/room/invited.rs index a7ea8c5c0d7..a99e210c69a 100644 --- a/crates/matrix-sdk/src/room/invited.rs +++ b/crates/matrix-sdk/src/room/invited.rs @@ -5,14 +5,14 @@ use thiserror::Error; use super::{Joined, Left}; use crate::{ room::{Common, RoomMember}, - BaseRoom, Client, Error, Result, RoomType, + BaseRoom, Client, Error, Result, RoomState, }; /// A room in the invited state. /// -/// This struct contains all methods specific to a `Room` with type -/// `RoomType::Invited`. Operations may fail once the underlying `Room` changes -/// `RoomType`. +/// This struct contains all methods specific to a `Room` with +/// `RoomState::Invited`. Operations may fail once the underlying `Room` changes +/// `RoomState`. #[derive(Debug, Clone)] pub struct Invited { pub(crate) inner: Common, @@ -37,15 +37,15 @@ pub enum InvitationError { } impl Invited { - /// Create a new `room::Invited` if the underlying `Room` has type - /// `RoomType::Invited`. + /// Create a new `room::Invited` if the underlying `Room` has + /// `RoomState::Invited`. /// /// # Arguments /// * `client` - The client used to make requests. /// /// * `room` - The underlying room. pub(crate) fn new(client: &Client, room: BaseRoom) -> Option { - if room.room_type() == RoomType::Invited { + if room.state() == RoomState::Invited { Some(Self { inner: Common::new(client.clone(), room) }) } else { None diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index 86eb8eb8bbe..7b8a279772f 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -39,7 +39,7 @@ use crate::{ attachment::AttachmentConfig, error::{Error, HttpResult}, room::Common, - BaseRoom, Client, Result, RoomType, + BaseRoom, Client, Result, RoomState, }; #[cfg(feature = "image-proc")] use crate::{ @@ -52,9 +52,9 @@ const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3); /// A room in the joined state. /// -/// The `JoinedRoom` contains all methods specific to a `Room` with type -/// `RoomType::Joined`. Operations may fail once the underlying `Room` changes -/// `RoomType`. +/// The `JoinedRoom` contains all methods specific to a `Room` with +/// `RoomState::Joined`. Operations may fail once the underlying `Room` changes +/// `RoomState`. #[derive(Debug, Clone)] pub struct Joined { pub(crate) inner: Common, @@ -69,15 +69,15 @@ impl Deref for Joined { } impl Joined { - /// Create a new `room::Joined` if the underlying `BaseRoom` has type - /// `RoomType::Joined`. + /// Create a new `room::Joined` if the underlying `BaseRoom` has + /// `RoomState::Joined`. /// /// # Arguments /// * `client` - The client used to make requests. /// /// * `room` - The underlying room. pub(crate) fn new(client: &Client, room: BaseRoom) -> Option { - if room.room_type() == RoomType::Joined { + if room.state() == RoomState::Joined { Some(Self { inner: Common::new(client.clone(), room) }) } else { None @@ -432,7 +432,7 @@ impl Joined { /// room anymore! #[instrument(skip_all, parent = &self.client.inner.root_span)] pub async fn sync_up(&self) { - while !self.is_synced() && self.room_type() == RoomType::Joined { + while !self.is_synced() && self.state() == RoomState::Joined { self.client.inner.sync_beat.listen().wait_timeout(Duration::from_secs(1)); } } diff --git a/crates/matrix-sdk/src/room/left.rs b/crates/matrix-sdk/src/room/left.rs index 97875e37644..9690262e87d 100644 --- a/crates/matrix-sdk/src/room/left.rs +++ b/crates/matrix-sdk/src/room/left.rs @@ -3,28 +3,28 @@ use std::ops::Deref; use ruma::api::client::membership::forget_room; use super::Joined; -use crate::{room::Common, BaseRoom, Client, Result, RoomType}; +use crate::{room::Common, BaseRoom, Client, Result, RoomState}; /// A room in the left state. /// -/// This struct contains all methods specific to a `Room` with type -/// `RoomType::Left`. Operations may fail once the underlying `Room` changes -/// `RoomType`. +/// This struct contains all methods specific to a `Room` with +/// `RoomState::Left`. Operations may fail once the underlying `Room` changes +/// `RoomState`. #[derive(Debug, Clone)] pub struct Left { pub(crate) inner: Common, } impl Left { - /// Create a new `room::Left` if the underlying `Room` has type - /// `RoomType::Left`. + /// Create a new `room::Left` if the underlying `Room` has + /// `RoomState::Left`. /// /// # Arguments /// * `client` - The client used to make requests. /// /// * `room` - The underlying room. pub(crate) fn new(client: &Client, room: BaseRoom) -> Option { - if room.room_type() == RoomType::Left { + if room.state() == RoomState::Left { Some(Self { inner: Common::new(client.clone(), room) }) } else { None diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index d2211f0e6f9..333b7f737c8 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -2,7 +2,7 @@ use std::ops::Deref; -use crate::RoomType; +use crate::RoomState; mod common; mod invited; @@ -45,10 +45,10 @@ impl Deref for Room { impl From for Room { fn from(room: Common) -> Self { - match room.room_type() { - RoomType::Joined => Self::Joined(Joined { inner: room }), - RoomType::Left => Self::Left(Left { inner: room }), - RoomType::Invited => Self::Invited(Invited { inner: room }), + match room.state() { + RoomState::Joined => Self::Joined(Joined { inner: room }), + RoomState::Left => Self::Left(Left { inner: room }), + RoomState::Invited => Self::Invited(Invited { inner: room }), } } } @@ -56,10 +56,10 @@ impl From for Room { impl From for Room { fn from(room: Joined) -> Self { let room = (*room).clone(); - match room.room_type() { - RoomType::Joined => Self::Joined(Joined { inner: room }), - RoomType::Left => Self::Left(Left { inner: room }), - RoomType::Invited => Self::Invited(Invited { inner: room }), + match room.state() { + RoomState::Joined => Self::Joined(Joined { inner: room }), + RoomState::Left => Self::Left(Left { inner: room }), + RoomState::Invited => Self::Invited(Invited { inner: room }), } } } @@ -67,10 +67,10 @@ impl From for Room { impl From for Room { fn from(room: Left) -> Self { let room = (*room).clone(); - match room.room_type() { - RoomType::Joined => Self::Joined(Joined { inner: room }), - RoomType::Left => Self::Left(Left { inner: room }), - RoomType::Invited => Self::Invited(Invited { inner: room }), + match room.state() { + RoomState::Joined => Self::Joined(Joined { inner: room }), + RoomState::Left => Self::Left(Left { inner: room }), + RoomState::Invited => Self::Invited(Invited { inner: room }), } } } @@ -78,10 +78,10 @@ impl From for Room { impl From for Room { fn from(room: Invited) -> Self { let room = (*room).clone(); - match room.room_type() { - RoomType::Joined => Self::Joined(Joined { inner: room }), - RoomType::Left => Self::Left(Left { inner: room }), - RoomType::Invited => Self::Invited(Invited { inner: room }), + match room.state() { + RoomState::Joined => Self::Joined(Joined { inner: room }), + RoomState::Left => Self::Left(Left { inner: room }), + RoomState::Invited => Self::Invited(Invited { inner: room }), } } } diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs index a6506082d4a..0ea46b04e3c 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -9,7 +9,7 @@ use matrix_sdk::{ api::client::room::create_room::v3::Request as CreateRoomRequest, events::room::member::{MembershipState, StrippedRoomMemberEvent}, }, - Client, RoomType, + Client, RoomState, }; use tokio::sync::Notify; @@ -136,7 +136,7 @@ async fn signal_on_invite( return; } - if room.room_type() != RoomType::Invited { + if room.state() != RoomState::Invited { return; } From 84d83dda0a8d4d763869de6b2464b02fd1d327b0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 12:21:44 +0100 Subject: [PATCH 02/28] base: Add RoomInfo::state accessor --- crates/matrix-sdk-base/Changelog.md | 1 + crates/matrix-sdk-base/src/rooms/normal.rs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-base/Changelog.md b/crates/matrix-sdk-base/Changelog.md index b10574bf534..138d3f7d7d8 100644 --- a/crates/matrix-sdk-base/Changelog.md +++ b/crates/matrix-sdk-base/Changelog.md @@ -3,6 +3,7 @@ ## unreleased - Rename `RoomType` to `RoomState` +- Add `RoomInfo::state` accessor ## 0.5.1 diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 1f6b10d6520..2db39edea30 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -648,7 +648,12 @@ impl RoomInfo { } } - /// Returns whether this is an encrypted Room. + /// Returns the state this room is in. + pub fn state(&self) -> RoomState { + self.room_state + } + + /// Returns whether this is an encrypted room. pub fn is_encrypted(&self) -> bool { self.base_info.encryption.is_some() } From 84ff8980f3fd87632b22aaeaf6f2b074dfcd51fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 13 Mar 2023 10:36:25 +0100 Subject: [PATCH 03/28] sdk-base: Expose calculated push actions on event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- crates/matrix-sdk-base/src/client.rs | 9 +------ .../src/deserialized_responses.rs | 21 +++++++++++++--- crates/matrix-sdk-crypto/src/machine.rs | 6 ++++- crates/matrix-sdk/src/room/common.rs | 25 +++++++++++-------- .../src/room/timeline/tests/basic.rs | 2 +- .../matrix-sdk/src/room/timeline/tests/mod.rs | 7 ++++-- crates/matrix-sdk/src/sliding_sync/room.rs | 1 + 7 files changed, 44 insertions(+), 27 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 8af784eb4f9..3d1be6b74d7 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -423,14 +423,7 @@ impl BaseClient { ), ); } - // TODO if there is an - // Action::SetTweak(Tweak::Highlight) we need to store - // its value with the event so a client can show if the - // event is highlighted - // in the UI. - // Requires the possibility to associate custom data - // with events and to - // store them. + event.push_actions = actions.to_owned(); } } Err(e) => { diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 91bf0eaa28c..b3d6ff29d9a 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use ruma::{ events::{AnySyncTimelineEvent, AnyTimelineEvent}, + push::Action, serde::Raw, DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedUserId, }; @@ -59,6 +60,9 @@ pub struct SyncTimelineEvent { /// The encryption info about the event. Will be `None` if the event was not /// encrypted. pub encryption_info: Option, + /// The push actions associated with this event. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub push_actions: Vec, } impl SyncTimelineEvent { @@ -71,7 +75,7 @@ impl SyncTimelineEvent { impl From> for SyncTimelineEvent { fn from(inner: Raw) -> Self { - Self { encryption_info: None, event: inner } + Self { encryption_info: None, event: inner, push_actions: Vec::default() } } } @@ -81,7 +85,11 @@ impl From for SyncTimelineEvent { // `TimelineEvent` without the `room_id`. By converting the raw value in // this way, we simply cause the `room_id` field in the json to be // ignored by a subsequent deserialization. - Self { encryption_info: o.encryption_info, event: o.event.cast() } + Self { + encryption_info: o.encryption_info, + event: o.event.cast(), + push_actions: o.push_actions, + } } } @@ -92,6 +100,8 @@ pub struct TimelineEvent { /// The encryption info about the event. Will be `None` if the event was not /// encrypted. pub encryption_info: Option, + /// The push actions associated with this event. + pub push_actions: Vec, } #[cfg(test)] @@ -115,8 +125,11 @@ mod tests { "sender": "@carl:example.com", }); - let room_event = - TimelineEvent { event: Raw::new(&event).unwrap().cast(), encryption_info: None }; + let room_event = TimelineEvent { + event: Raw::new(&event).unwrap().cast(), + encryption_info: None, + push_actions: Vec::default(), + }; let converted_room_event: SyncTimelineEvent = room_event.into(); diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index fb8afe13af6..8bb322fd0cb 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1178,7 +1178,11 @@ impl OlmMachine { let (decrypted_event, _) = session.decrypt(event).await?; let encryption_info = self.get_encryption_info(&session, &event.sender).await?; - Ok(TimelineEvent { encryption_info: Some(encryption_info), event: decrypted_event }) + Ok(TimelineEvent { + encryption_info: Some(encryption_info), + event: decrypted_event, + push_actions: Vec::default(), + }) } else { Err(MegolmError::MissingRoomKey) } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 60c07096374..c633ed4879b 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -211,7 +211,11 @@ impl Common { chunk: http_response .chunk .into_iter() - .map(|event| TimelineEvent { event, encryption_info: None }) + .map(|event| TimelineEvent { + event, + encryption_info: None, + push_actions: Vec::default(), + }) .collect(), #[cfg(feature = "e2e-encryption")] chunk: Vec::with_capacity(http_response.chunk.len()), @@ -228,21 +232,20 @@ impl Common { if let Ok(event) = machine.decrypt_room_event(event.cast_ref(), room_id).await { event } else { - TimelineEvent { event, encryption_info: None } + TimelineEvent { event, encryption_info: None, push_actions: Vec::default() } } } else { - TimelineEvent { event, encryption_info: None } + TimelineEvent { event, encryption_info: None, push_actions: Vec::default() } }; response.chunk.push(decrypted_event); } } else { - response.chunk.extend( - http_response - .chunk - .into_iter() - .map(|event| TimelineEvent { event, encryption_info: None }), - ); + response.chunk.extend(http_response.chunk.into_iter().map(|event| TimelineEvent { + event, + encryption_info: None, + push_actions: Vec::default(), + })); } Ok(response) @@ -291,11 +294,11 @@ impl Common { return Ok(event); } } - Ok(TimelineEvent { event, encryption_info: None }) + Ok(TimelineEvent { event, encryption_info: None, push_actions: Vec::default() }) } #[cfg(not(feature = "e2e-encryption"))] - Ok(TimelineEvent { event, encryption_info: None }) + Ok(TimelineEvent { event, encryption_info: None, push_actions: Vec::default() }) } pub(crate) async fn request_members(&self) -> Result> { diff --git a/crates/matrix-sdk/src/room/timeline/tests/basic.rs b/crates/matrix-sdk/src/room/timeline/tests/basic.rs index 99ce74850d6..e8ead03fec0 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/basic.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/basic.rs @@ -30,7 +30,7 @@ use crate::room::timeline::{ fn sync_timeline_event(event: JsonValue) -> SyncTimelineEvent { let event = serde_json::from_value(event).unwrap(); - SyncTimelineEvent { event, encryption_info: None } + SyncTimelineEvent { event, encryption_info: None, push_actions: Vec::default() } } #[async_test] diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index 15e9bb717ef..00c8ac746c5 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -163,8 +163,11 @@ impl TestTimeline { } async fn handle_back_paginated_custom_event(&self, event: JsonValue) { - let timeline_event = - TimelineEvent { event: Raw::new(&event).unwrap().cast(), encryption_info: None }; + let timeline_event = TimelineEvent { + event: Raw::new(&event).unwrap().cast(), + encryption_info: None, + push_actions: Vec::default(), + }; self.inner.handle_back_paginated_event(timeline_event).await; } diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 19c1e272801..1abaa7dd8e7 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -380,6 +380,7 @@ mod tests { .unwrap() .cast(), encryption_info: None, + push_actions: Vec::default(), } .into()], }; From d5a56bcbc7235528804452869aebe3cf6115b608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 13 Mar 2023 11:39:50 +0100 Subject: [PATCH 04/28] sdk: Expose calculated push actions for event handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kévin Commaille --- crates/matrix-sdk/src/client/mod.rs | 11 +++++ .../matrix-sdk/src/event_handler/context.rs | 7 +++ crates/matrix-sdk/src/event_handler/mod.rs | 43 +++++++++++++++---- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 1f3fb62a108..a1c549d4944 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -579,6 +579,7 @@ impl Client { /// push_rules::PushRulesEvent, /// room::{message::SyncRoomMessageEvent, topic::SyncRoomTopicEvent}, /// }, + /// push::Action, /// Int, MilliSecondsSinceUnixEpoch, /// }, /// Client, @@ -606,6 +607,16 @@ impl Client { /// } /// }, /// ); + /// client.add_event_handler( + /// |ev: SyncRoomMessageEvent, room: Room, push_actions: Vec| { + /// async move { + /// // A `Vec` parameter allows you to know which push actions + /// // are applicable for an event. For example, an event with + /// // `Action::SetTweak(Tweak::Highlight(true))` should be highlighted + /// // in the timeline. + /// } + /// }, + /// ); /// client.add_event_handler(|ev: SyncRoomTopicEvent| async move { /// // You can omit any or all arguments after the first. /// }); diff --git a/crates/matrix-sdk/src/event_handler/context.rs b/crates/matrix-sdk/src/event_handler/context.rs index 0b4dd5fe093..7e02d54179d 100644 --- a/crates/matrix-sdk/src/event_handler/context.rs +++ b/crates/matrix-sdk/src/event_handler/context.rs @@ -16,6 +16,7 @@ use std::ops::Deref; use matrix_sdk_base::deserialized_responses::EncryptionInfo; +use ruma::push::Action; use serde_json::value::RawValue as RawJsonValue; use super::{EventHandlerData, EventHandlerHandle}; @@ -81,6 +82,12 @@ impl EventHandlerContext for Option { } } +impl EventHandlerContext for Vec { + fn from_data(data: &EventHandlerData<'_>) -> Option { + Some(data.push_actions.to_owned()) + } +} + /// A custom value registered with /// [`.add_event_handler_context`][Client::add_event_handler_context]. #[derive(Debug)] diff --git a/crates/matrix-sdk/src/event_handler/mod.rs b/crates/matrix-sdk/src/event_handler/mod.rs index b9bdfec214c..01742de947a 100644 --- a/crates/matrix-sdk/src/event_handler/mod.rs +++ b/crates/matrix-sdk/src/event_handler/mod.rs @@ -50,7 +50,7 @@ use matrix_sdk_base::{ deserialized_responses::{EncryptionInfo, SyncTimelineEvent}, SendOutsideWasm, SyncOutsideWasm, }; -use ruma::{events::AnySyncStateEvent, serde::Raw, OwnedRoomId}; +use ruma::{events::AnySyncStateEvent, push::Action, serde::Raw, OwnedRoomId}; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::value::RawValue as RawJsonValue; use tracing::{debug, error, field::debug, instrument, warn}; @@ -234,6 +234,7 @@ pub struct EventHandlerData<'a> { room: Option, raw: &'a RawJsonValue, encryption_info: Option<&'a EncryptionInfo>, + push_actions: &'a [Action], handle: EventHandlerHandle, } @@ -338,7 +339,7 @@ impl Client { for raw_event in events { let event_type = raw_event.deserialize_as::>()?.event_type; - self.call_event_handlers(room, raw_event.json(), kind, &event_type, None).await; + self.call_event_handlers(room, raw_event.json(), kind, &event_type, None, &[]).await; } Ok(()) @@ -365,7 +366,8 @@ impl Client { let redacted = unsigned.and_then(|u| u.redacted_because).is_some(); let handler_kind = HandlerKind::state_redacted(redacted); - self.call_event_handlers(room, raw_event.json(), handler_kind, &event_type, None).await; + self.call_event_handlers(room, raw_event.json(), handler_kind, &event_type, None, &[]) + .await; } Ok(()) @@ -396,18 +398,41 @@ impl Client { let raw_event = item.event.json(); let encryption_info = item.encryption_info.as_ref(); + let push_actions = &item.push_actions; // Event handlers for possibly-redacted timeline events - self.call_event_handlers(room, raw_event, handler_kind_g, &event_type, encryption_info) - .await; + self.call_event_handlers( + room, + raw_event, + handler_kind_g, + &event_type, + encryption_info, + push_actions, + ) + .await; // Event handlers specifically for redacted OR unredacted timeline events - self.call_event_handlers(room, raw_event, handler_kind_r, &event_type, encryption_info) - .await; + self.call_event_handlers( + room, + raw_event, + handler_kind_r, + &event_type, + encryption_info, + push_actions, + ) + .await; // Event handlers for `AnySyncTimelineEvent` let kind = HandlerKind::Timeline; - self.call_event_handlers(room, raw_event, kind, &event_type, encryption_info).await; + self.call_event_handlers( + room, + raw_event, + kind, + &event_type, + encryption_info, + push_actions, + ) + .await; } Ok(()) @@ -421,6 +446,7 @@ impl Client { event_kind: HandlerKind, event_type: &str, encryption_info: Option<&EncryptionInfo>, + push_actions: &[Action], ) { let room_id = room.as_ref().map(|r| r.room_id()); if let Some(room_id) = room_id { @@ -441,6 +467,7 @@ impl Client { room: room.clone(), raw, encryption_info, + push_actions, handle, }; From 929538608aae8087c89b5fa055666e5c2b8ff075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 13 Mar 2023 12:52:44 +0100 Subject: [PATCH 05/28] timeline: Expose if an event should be highlighted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only events received via sync. Signed-off-by: Kévin Commaille --- .../matrix-sdk/src/room/timeline/builder.rs | 13 ++-- .../src/room/timeline/event_handler.rs | 2 + .../src/room/timeline/event_item/remote.rs | 9 +++ crates/matrix-sdk/src/room/timeline/inner.rs | 12 +++ .../matrix-sdk/src/room/timeline/tests/mod.rs | 16 ++-- .../tests/integration/room/timeline.rs | 73 +++++++++++++++++++ 6 files changed, 112 insertions(+), 13 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/builder.rs b/crates/matrix-sdk/src/room/timeline/builder.rs index 13073766e34..84cbd5f7f2a 100644 --- a/crates/matrix-sdk/src/room/timeline/builder.rs +++ b/crates/matrix-sdk/src/room/timeline/builder.rs @@ -19,9 +19,12 @@ use matrix_sdk_base::{ deserialized_responses::{EncryptionInfo, SyncTimelineEvent}, locks::Mutex, }; -use ruma::events::{ - fully_read::FullyReadEventContent, - receipt::{ReceiptThread, ReceiptType, SyncReceiptEvent}, +use ruma::{ + events::{ + fully_read::FullyReadEventContent, + receipt::{ReceiptThread, ReceiptType, SyncReceiptEvent}, + }, + push::Action, }; use tracing::error; @@ -124,10 +127,10 @@ impl TimelineBuilder { let timeline_event_handle = room.add_event_handler({ let inner = inner.clone(); - move |event, encryption_info: Option| { + move |event, encryption_info: Option, push_actions: Vec| { let inner = inner.clone(); async move { - inner.handle_live_event(event, encryption_info).await; + inner.handle_live_event(event, encryption_info, push_actions).await; } } }); diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 411e8e86528..1487bc94b97 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -76,6 +76,7 @@ pub(super) struct TimelineEventMetadata { pub(super) relations: BundledRelations, pub(super) encryption_info: Option, pub(super) read_receipts: IndexMap, + pub(super) is_highlighted: bool, } #[derive(Clone)] @@ -575,6 +576,7 @@ impl<'a> TimelineEventHandler<'a> { self.meta.is_own_event, self.meta.encryption_info.clone(), raw_event.clone(), + self.meta.is_highlighted, )) } }; diff --git a/crates/matrix-sdk/src/room/timeline/event_item/remote.rs b/crates/matrix-sdk/src/room/timeline/event_item/remote.rs index e3feb991e88..e824a44ff83 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item/remote.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item/remote.rs @@ -38,6 +38,8 @@ pub struct RemoteEventTimelineItem { encryption_info: Option, // FIXME: Expose the raw JSON of aggregated events somehow raw: Raw, + /// Whether the item should be highlighted in the timeline. + is_highlighted: bool, } impl RemoteEventTimelineItem { @@ -53,6 +55,7 @@ impl RemoteEventTimelineItem { is_own: bool, encryption_info: Option, raw: Raw, + is_highlighted: bool, ) -> Self { Self { event_id, @@ -65,6 +68,7 @@ impl RemoteEventTimelineItem { is_own, encryption_info, raw, + is_highlighted, } } @@ -125,6 +129,11 @@ impl RemoteEventTimelineItem { &self.raw } + /// Whether the event should be highlighted in the timeline. + pub fn is_highlighted(&self) -> bool { + self.is_highlighted + } + pub(in crate::room::timeline) fn set_content(&mut self, content: TimelineItemContent) { self.content = content; } diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index b5e43af2bf6..4daccfd2187 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -35,6 +35,7 @@ use ruma::{ relation::Annotation, AnyMessageLikeEventContent, AnySyncTimelineEvent, }, + push::{Action, Tweak}, serde::Raw, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, TransactionId, UserId, @@ -145,6 +146,7 @@ impl TimelineInner

{ handle_remote_event( event.event, event.encryption_info, + event.push_actions, TimelineItemPosition::End, state, &self.room_data_provider, @@ -170,11 +172,13 @@ impl TimelineInner

{ &self, raw: Raw, encryption_info: Option, + push_actions: Vec, ) { let mut state = self.state.lock().await; handle_remote_event( raw, encryption_info, + push_actions, TimelineItemPosition::End, &mut state, &self.room_data_provider, @@ -200,6 +204,8 @@ impl TimelineInner

{ // FIXME: Should we supply something here for encrypted rooms? encryption_info: None, read_receipts: Default::default(), + // An event sent by ourself is never matched against push rules. + is_highlighted: false, }; let flow = Flow::Local { txn_id, timestamp: MilliSecondsSinceUnixEpoch::now() }; @@ -267,6 +273,7 @@ impl TimelineInner

{ handle_remote_event( event.event.cast(), event.encryption_info, + event.push_actions, TimelineItemPosition::Start, &mut state, &self.room_data_provider, @@ -412,6 +419,7 @@ impl TimelineInner

{ let result = handle_remote_event( event.event.cast(), event.encryption_info, + event.push_actions, TimelineItemPosition::Update(idx), &mut state, &self.room_data_provider, @@ -642,6 +650,7 @@ impl RoomDataProvider for room::Common { async fn handle_remote_event( raw: Raw, encryption_info: Option, + push_actions: Vec, position: TimelineItemPosition, timeline_state: &mut TimelineInnerState, room_data_provider: &P, @@ -682,6 +691,8 @@ async fn handle_remote_event( } else { Default::default() }; + let is_highlighted = + push_actions.iter().any(|a| matches!(a, Action::SetTweak(Tweak::Highlight(true)))); let event_meta = TimelineEventMetadata { sender, sender_profile, @@ -689,6 +700,7 @@ async fn handle_remote_event( relations, encryption_info, read_receipts, + is_highlighted, }; let flow = Flow::Remote { event_id, origin_server_ts, raw_event: raw, txn_id, position }; diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index 00c8ac746c5..ebba28a5341 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -81,7 +81,7 @@ impl TestTimeline { { let ev = self.make_message_event(sender, content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_redacted_message_event(&self, sender: &UserId, content: C) @@ -90,7 +90,7 @@ impl TestTimeline { { let ev = self.make_redacted_message_event(sender, content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_state_event(&self, sender: &UserId, content: C, prev_content: Option) @@ -99,7 +99,7 @@ impl TestTimeline { { let ev = self.make_state_event(sender, "", content, prev_content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_state_event_with_state_key( @@ -113,7 +113,7 @@ impl TestTimeline { { let ev = self.make_state_event(sender, state_key.as_ref(), content, prev_content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_redacted_state_event(&self, sender: &UserId, content: C) @@ -122,7 +122,7 @@ impl TestTimeline { { let ev = self.make_redacted_state_event(sender, "", content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_redacted_state_event_with_state_key( @@ -135,12 +135,12 @@ impl TestTimeline { { let ev = self.make_redacted_state_event(sender, state_key.as_ref(), content); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_custom_event(&self, event: JsonValue) { let raw = Raw::new(&event).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_live_redaction(&self, sender: &UserId, redacts: &EventId) { @@ -153,7 +153,7 @@ impl TestTimeline { "origin_server_ts": self.next_server_ts(), }); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None).await; + self.inner.handle_live_event(raw, None, Vec::default()).await; } async fn handle_local_event(&self, content: AnyMessageLikeEventContent) -> OwnedTransactionId { diff --git a/crates/matrix-sdk/tests/integration/room/timeline.rs b/crates/matrix-sdk/tests/integration/room/timeline.rs index e56533050d6..860ac9cc57f 100644 --- a/crates/matrix-sdk/tests/integration/room/timeline.rs +++ b/crates/matrix-sdk/tests/integration/room/timeline.rs @@ -883,3 +883,76 @@ async fn read_receipts_updates() { let third_event = third_item.as_event().unwrap().as_remote().unwrap(); assert_eq!(third_event.read_receipts().len(), 2); } + +#[async_test] +async fn sync_highlighted() { + let room_id = room_id!("!a98sd12bjh:example.org"); + let (client, server) = logged_in_client().await; + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let mut ev_builder = EventBuilder::new(); + ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id)); + + mock_sync(&server, ev_builder.build_json_sync_response(), None).await; + let _response = client.sync_once(sync_settings.clone()).await.unwrap(); + server.reset().await; + + let room = client.get_room(room_id).unwrap(); + let timeline = room.timeline().await; + let (_, mut timeline_stream) = timeline.subscribe().await; + + ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( + TimelineTestEvent::Custom(json!({ + "content": { + "body": "hello", + "msgtype": "m.text", + }, + "event_id": "$msda7m0df9E9op3", + "origin_server_ts": 152037280, + "sender": "@example:localhost", + "type": "m.room.message", + })), + )); + + mock_sync(&server, ev_builder.build_json_sync_response(), None).await; + let _response = client.sync_once(sync_settings.clone()).await.unwrap(); + server.reset().await; + + let _day_divider = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::PushBack { value }) => value + ); + let first = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::PushBack { value }) => value + ); + let remote_event = first.as_event().unwrap().as_remote().unwrap(); + // Own events don't trigger push rules. + assert!(!remote_event.is_highlighted()); + + ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event( + TimelineTestEvent::Custom(json!({ + "content": { + "body": "This room has been replaced", + "replacement_room": "!newroom:localhost", + }, + "event_id": "$foun39djjod0f", + "origin_server_ts": 152039280, + "sender": "@bob:localhost", + "state_key": "", + "type": "m.room.tombstone", + })), + )); + + mock_sync(&server, ev_builder.build_json_sync_response(), None).await; + let _response = client.sync_once(sync_settings.clone()).await.unwrap(); + server.reset().await; + + let second = assert_matches!( + timeline_stream.next().await, + Some(VectorDiff::PushBack { value }) => value + ); + let remote_event = second.as_event().unwrap().as_remote().unwrap(); + // `m.room.tombstone` should be highlighted by default. + assert!(!remote_event.is_highlighted()); +} From 6afb6f1e27b1c9630ebc8c3a2318f56b288b4e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 9 Mar 2023 12:06:31 +0100 Subject: [PATCH 06/28] Use an Option for the KeysQueryDetails instead of a default value The Option conveys our intention a bit better compared to the default value. While nothing bad can happen, since the request IDs map will be empty by default, it's a bit scary to have a valid sequence number since an i64 defaults to 0. --- .../src/identities/manager.rs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/manager.rs b/crates/matrix-sdk-crypto/src/identities/manager.rs index 41bedaca7b1..875a5ba9c57 100644 --- a/crates/matrix-sdk-crypto/src/identities/manager.rs +++ b/crates/matrix-sdk-crypto/src/identities/manager.rs @@ -128,7 +128,7 @@ pub(crate) struct IdentityManager { store: Store, /// Details of the current "in-flight" key query request, if any - keys_query_request_details: Arc>, + keys_query_request_details: Arc>>, } /// Details of an in-flight key query request @@ -149,7 +149,7 @@ impl IdentityManager { pub fn new(user_id: Arc, device_id: Arc, store: Store) -> Self { let keys_query_listener = KeysQueryListener::new(store.clone()); - let keys_query_request_details = Mutex::new(KeysQueryRequestDetails::default()); + let keys_query_request_details = Mutex::new(None); IdentityManager { user_id, @@ -223,11 +223,19 @@ impl IdentityManager { // if this request is one of those we expected to be in flight, pass the // sequence number back to the store so that it can mark devices up to // date - let (sequence_number, removed) = { + let sequence_number = { let mut request_details = self.keys_query_request_details.lock().await; - (request_details.sequence_number, request_details.request_ids.remove(request_id)) + + request_details.as_mut().and_then(|details| { + if details.request_ids.remove(request_id) { + Some(details.sequence_number) + } else { + None + } + }) }; - if removed { + + if let Some(sequence_number) = sequence_number { self.store .mark_tracked_users_as_up_to_date( response.device_keys.keys().map(Deref::deref), @@ -696,8 +704,8 @@ impl IdentityManager { pub async fn users_for_key_query( &self, ) -> StoreResult> { - // forget about any previous key queries in flight - *(self.keys_query_request_details.lock().await) = KeysQueryRequestDetails::default(); + // Forget about any previous key queries in flight. + *self.keys_query_request_details.lock().await = None; let (users, sequence_number) = self.store.users_for_key_query().await?; @@ -732,7 +740,7 @@ impl IdentityManager { result.push((request_id.clone(), req)); request_details.request_ids.insert(request_id); } - *(self.keys_query_request_details.lock().await) = request_details; + *(self.keys_query_request_details.lock().await) = Some(request_details); Ok(result) } From 5f1a22e26fa0f5973a15ad4facd2c3b85a3f4570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 9 Mar 2023 12:09:11 +0100 Subject: [PATCH 07/28] Remove some useless ? -> Ok invocations We can just return the result directly instead of doing the error conversion dance using the question mark operator --- crates/matrix-sdk-crypto/src/store/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 467cba2d182..2daeb9e8450 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -738,17 +738,18 @@ impl Store { users: impl Iterator, ) -> Result<()> { self.load_tracked_users().await?; - let mut store_updates: Vec<(&UserId, bool)> = Vec::new(); + let mut store_updates: Vec<(&UserId, bool)> = Vec::new(); let mut key_query_lock = self.users_for_key_query.lock().await; + for user_id in users { if self.tracked_users_cache.contains(user_id) { key_query_lock.insert_user(user_id); store_updates.push((user_id, true)); } } - self.inner.save_tracked_users(&store_updates).await?; - Ok(()) + + self.inner.save_tracked_users(&store_updates).await } /// Flag that the given users devices are now up-to-date. @@ -763,15 +764,15 @@ impl Store { ) -> Result<()> { let mut store_updates: Vec<(&UserId, bool)> = Vec::new(); let mut key_query_lock = self.users_for_key_query.lock().await; + for user_id in users { if self.tracked_users_cache.contains(user_id) { let clean = key_query_lock.maybe_remove_user(user_id, sequence_number); store_updates.push((user_id, !clean)); } } - self.inner.save_tracked_users(&store_updates).await?; - Ok(()) + self.inner.save_tracked_users(&store_updates).await } /// Load the list of users for whom we are tracking their device lists and From 5d6cf2d33ae2ec635aac0f437bc960adf7a6d96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 9 Mar 2023 12:49:27 +0100 Subject: [PATCH 08/28] Remove some clones when creating a /keys/query request The part of code that converts a list of users to the actual /keys/query request uses the chunks() method. This method operates on the slice. Our list/vec of users gets dereferenced into a slice before we create our chunks. The chunks can't take ownership of the data, which in turn requires us to clone the user IDs for them to be put into the request. Itertools has a chunks() method which operates on an iterator which we can utilize to remove, not only the clone, but also a collect call. At the same time, let's make the conversion step a simple functional mapping and document what's going on. --- Cargo.lock | 1 + crates/matrix-sdk-crypto/Cargo.toml | 1 + .../src/identities/manager.rs | 66 ++++++++++++------- crates/matrix-sdk-crypto/src/requests.rs | 4 +- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 232ca8abb44..92e73a0a8a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2745,6 +2745,7 @@ dependencies = [ "hmac", "http", "indoc", + "itertools", "matrix-sdk-common", "matrix-sdk-qrcode", "matrix-sdk-test", diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index c3a5e3283c3..f8e8d0d747d 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -39,6 +39,7 @@ futures-core = "0.3.24" futures-util = { workspace = true } hmac = "0.12.1" http = { workspace = true, optional = true } # feature = testing only +itertools = "0.10.5" matrix-sdk-qrcode = { version = "0.4.0", path = "../matrix-sdk-qrcode", optional = true } matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } olm-rs = { version = "2.2.0", features = ["serde"], optional = true } diff --git a/crates/matrix-sdk-crypto/src/identities/manager.rs b/crates/matrix-sdk-crypto/src/identities/manager.rs index 875a5ba9c57..30da8a8fb91 100644 --- a/crates/matrix-sdk-crypto/src/identities/manager.rs +++ b/crates/matrix-sdk-crypto/src/identities/manager.rs @@ -20,6 +20,7 @@ use std::{ }; use futures_util::future::join_all; +use itertools::Itertools; use matrix_sdk_common::{ executor::spawn, locks::Mutex, @@ -694,7 +695,7 @@ impl IdentityManager { /// /// # Returns /// - /// A list of pairs of `(request_id, request)` + /// A map of a request ID to the `/keys/query` request. /// /// The response of a successful key query requests needs to be passed to /// the [`OlmMachine`] with the [`receive_keys_query_response`]. @@ -703,7 +704,7 @@ impl IdentityManager { /// [`receive_keys_query_response`]: #method.receive_keys_query_response pub async fn users_for_key_query( &self, - ) -> StoreResult> { + ) -> StoreResult> { // Forget about any previous key queries in flight. *self.keys_query_request_details.lock().await = None; @@ -723,25 +724,44 @@ impl IdentityManager { }; if users.is_empty() { - return Ok(Vec::new()); - } - - let mut result: Vec<(OwnedTransactionId, KeysQueryRequest)> = Vec::new(); - let mut request_details = - KeysQueryRequestDetails { sequence_number, request_ids: HashSet::new() }; - - let filtered_users: Vec = - users.into_iter().filter(|u| !self.failures.contains(u.server_name())).collect(); - - for c in filtered_users.chunks(Self::MAX_KEY_QUERY_USERS) { - let request_id = TransactionId::new(); - let req = KeysQueryRequest::new(c.iter().map(|u| (u.clone(), Vec::new())).collect()); - debug!(?request_id, users = ?c, "Created keys query request"); - result.push((request_id.clone(), req)); - request_details.request_ids.insert(request_id); + Ok(BTreeMap::new()) + } else { + // Let's remove users that are part of the `FailuresCache`. The cache, which is + // a TTL cache, remembers users for which a previous `/key/query` request has + // failed. We don't retry a `/keys/query` for such users for a + // certain amount of time. + let users = users.into_iter().filter(|u| !self.failures.contains(u.server_name())); + + // We don't want to create a single `/keys/query` request with an infinite + // amount of users. Some servers will likely bail out after a + // certain amount of users and the responses will be large. In the + // case of a transmission error, we'll have to retransmit the large + // response. + // + // Convert the set of users into multiple /keys/query requests. + let requests: BTreeMap<_, _> = users + .chunks(Self::MAX_KEY_QUERY_USERS) + .into_iter() + .map(|user_chunk| { + let request_id = TransactionId::new(); + let request = KeysQueryRequest::new(user_chunk); + + debug!(?request_id, users = ?request.device_keys.keys(), "Created a /keys/query request"); + + (request_id, request) + }) + .collect(); + + // Collect the request IDs, these will be used later in the + // `receive_keys_query_response()` method to figure out if the user can be + // marked as up-to-date/non-dirty. + let request_ids = requests.keys().cloned().collect(); + let request_details = KeysQueryRequestDetails { sequence_number, request_ids }; + + *self.keys_query_request_details.lock().await = Some(request_details); + + Ok(requests) } - *(self.keys_query_request_details.lock().await) = Some(request_details); - Ok(result) } /// Receive the list of users that contained changed devices from the @@ -1161,7 +1181,7 @@ pub(crate) mod tests { manager.update_tracked_users([alice]).await.unwrap(); // alice should be in the list of key queries - let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap(); + let (reqid, req) = manager.users_for_key_query().await.unwrap().pop_first().unwrap(); assert!(req.device_keys.contains_key(alice)); // another invalidation turns up @@ -1171,7 +1191,7 @@ pub(crate) mod tests { manager.receive_keys_query_response(&reqid, &other_key_query()).await.unwrap(); // alice should *still* be in the list of key queries - let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap(); + let (reqid, req) = manager.users_for_key_query().await.unwrap().pop_first().unwrap(); assert!(req.device_keys.contains_key(alice)); // another key query response @@ -1197,7 +1217,7 @@ pub(crate) mod tests { manager.store.tracked_users().await.unwrap().contains(alice), "Alice is tracked after being marked as tracked" ); - let (reqid, req) = manager.users_for_key_query().await.unwrap().pop().unwrap(); + let (reqid, req) = manager.users_for_key_query().await.unwrap().pop_first().unwrap(); assert!(req.device_keys.contains_key(alice)); // a failure should stop us querying for the user's keys. diff --git a/crates/matrix-sdk-crypto/src/requests.rs b/crates/matrix-sdk-crypto/src/requests.rs index 5cec2b344e4..068cd58744e 100644 --- a/crates/matrix-sdk-crypto/src/requests.rs +++ b/crates/matrix-sdk-crypto/src/requests.rs @@ -192,7 +192,9 @@ pub struct KeysQueryRequest { } impl KeysQueryRequest { - pub(crate) fn new(device_keys: BTreeMap>) -> Self { + pub(crate) fn new(users: impl Iterator) -> Self { + let device_keys = users.map(|u| (u, Vec::new())).collect(); + Self { timeout: None, device_keys, token: None } } } From 58e99a645f27c1fdc3a52740acbf24a3aa16e656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 10 Mar 2023 11:15:04 +0100 Subject: [PATCH 09/28] Don't use a type alias for the sequence number Using a wrapper type allows us to hide the internals a bit better and we're now implementing Ord which is used in the comparison. --- .../src/identities/manager.rs | 7 +- crates/matrix-sdk-crypto/src/store/caches.rs | 147 +++++++++++++++++- crates/matrix-sdk-crypto/src/store/mod.rs | 90 +---------- 3 files changed, 154 insertions(+), 90 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/manager.rs b/crates/matrix-sdk-crypto/src/identities/manager.rs index 30da8a8fb91..5b63d344d88 100644 --- a/crates/matrix-sdk-crypto/src/identities/manager.rs +++ b/crates/matrix-sdk-crypto/src/identities/manager.rs @@ -40,7 +40,10 @@ use crate::{ }, olm::PrivateCrossSigningIdentity, requests::KeysQueryRequest, - store::{Changes, DeviceChanges, IdentityChanges, Result as StoreResult, Store}, + store::{ + caches::SequenceNumber, Changes, DeviceChanges, IdentityChanges, Result as StoreResult, + Store, + }, types::{CrossSigningKey, DeviceKeys, MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, utilities::FailuresCache, LocalTrust, SignatureError, @@ -137,7 +140,7 @@ pub(crate) struct IdentityManager { struct KeysQueryRequestDetails { /// The sequence number, to be passed to /// `Store.mark_tracked_users_as_up_to_date`. - sequence_number: i64, + sequence_number: SequenceNumber, /// A single batch of queries returned by the Store is broken up into one or /// more actual KeysQueryRequests, each with their own request id. We diff --git a/crates/matrix-sdk-crypto/src/store/caches.rs b/crates/matrix-sdk-crypto/src/store/caches.rs index 1b5fe4e0987..ae2e3d959b2 100644 --- a/crates/matrix-sdk-crypto/src/store/caches.rs +++ b/crates/matrix-sdk-crypto/src/store/caches.rs @@ -17,11 +17,15 @@ //! Note: You'll only be interested in these if you are implementing a custom //! `CryptoStore`. -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use dashmap::DashMap; use matrix_sdk_common::locks::Mutex; use ruma::{DeviceId, OwnedDeviceId, OwnedRoomId, OwnedUserId, RoomId, UserId}; +use tracing::{field::debug, instrument, trace, Span}; use crate::{ identities::ReadOnlyDevice, @@ -163,16 +167,125 @@ impl DeviceStore { } } +/// A numeric type that can represent an infinite ordered sequence. +/// +/// It uses wrapping arithmetic to make sure we never run out of numbers. (2**64 +/// should be enough for anyone, but it's easy enough just to make it wrap.) +// +/// Internally it uses a *signed* counter so that we can compare values via a +/// subtraction. For example, suppose we've just overflowed from i64::MAX to +/// i64::MIN. (i64::MAX.wrapping_sub(i64::MIN)) is -1, which tells us that +/// i64::MAX comes before i64::MIN in the sequence. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) struct SequenceNumber(i64); + +impl PartialOrd for SequenceNumber { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.wrapping_sub(other.0).cmp(&0)) + } +} + +impl Ord for SequenceNumber { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.wrapping_sub(other.0).cmp(&0) + } +} + +impl SequenceNumber { + fn increment(&mut self) { + self.0 = self.0.wrapping_add(1) + } + + fn previous(&self) -> Self { + Self(self.0.wrapping_sub(1)) + } +} + +/// Record of the users that are waiting for a /keys/query. +/// +/// To avoid races, we maintain a sequence number which is updated each time we +/// receive an invalidation notification. We also record the sequence number at +/// which each user was last invalidated. Then, we attach the current sequence +/// number to each `/keys/query` request, and when we get the response we can +/// tell if any users have been invalidated more recently than that request. +#[derive(Debug)] +pub(super) struct UsersForKeyQuery { + /// The sequence number we will assign to the next addition to user_map + next_sequence_number: SequenceNumber, + + /// The users pending a lookup, together with the sequence number at which + /// they were added to the list + user_map: HashMap, +} + +impl UsersForKeyQuery { + /// Create a new, empty, `UsersForKeyQueryCache` + pub(super) fn new() -> Self { + UsersForKeyQuery { next_sequence_number: Default::default(), user_map: Default::default() } + } + + /// Record a new user that requires a key query + pub(super) fn insert_user(&mut self, user: &UserId) { + let seq = self.next_sequence_number; + + trace!(?user, sequence_number = ?seq, "Flagging user for key query"); + + self.user_map.insert(user.to_owned(), seq); + self.next_sequence_number.increment(); + } + + /// Record that a user has received an update with the given sequence + /// number. + /// + /// If the sequence number is newer than the oldest invalidation for this + /// user, it is removed from the list of those needing an update. + /// + /// Returns true if the user is now up-to-date, else false + #[instrument(level = "trace", skip(self), fields(invalidation_sequence))] + pub(super) fn maybe_remove_user( + &mut self, + user: &UserId, + query_sequence: SequenceNumber, + ) -> bool { + let last_invalidation = self.user_map.get(user).copied(); + + if let Some(last_invalidation) = last_invalidation { + Span::current().record("invalidation_sequence", debug(last_invalidation)); + + if last_invalidation > query_sequence { + trace!("User invalidated since this query started: still not up-to-date"); + false + } else { + trace!("User now up-to-date"); + self.user_map.remove(user); + true + } + } else { + trace!("User already up-to-date, nothing to do"); + true + } + } + + /// Fetch the list of users waiting for a key query, and the current + /// sequence number + pub(super) fn users_for_key_query(&self) -> (HashSet, SequenceNumber) { + // we return the sequence number of the last invalidation + let sequence_number = self.next_sequence_number.previous(); + (self.user_map.keys().cloned().collect(), sequence_number) + } +} + #[cfg(test)] mod tests { use matrix_sdk_test::async_test; + use proptest::prelude::*; use ruma::room_id; use vodozemac::{Curve25519PublicKey, Ed25519PublicKey}; + use super::{DeviceStore, GroupSessionStore, SequenceNumber, SessionStore}; use crate::{ identities::device::testing::get_device, olm::{tests::get_account_and_session, InboundGroupSession}, - store::caches::{DeviceStore, GroupSessionStore, SessionStore}, }; #[async_test] @@ -263,4 +376,34 @@ mod tests { let loaded_device = store.get(device.user_id(), device.device_id()); assert!(loaded_device.is_none()); } + + #[test] + fn sequence_at_boundary() { + let first = SequenceNumber(i64::MAX); + let second = SequenceNumber(first.0.wrapping_add(1)); + let third = SequenceNumber(first.0.wrapping_sub(1)); + + assert!(second > first); + assert!(first < second); + assert!(third < first); + assert!(first > third); + assert!(second > third); + assert!(third < second); + } + + proptest! { + #[test] + fn partial_eq_sequence_number(sequence in i64::MIN..i64::MAX) { + let first = SequenceNumber(sequence); + let second = SequenceNumber(first.0.wrapping_add(1)); + let third = SequenceNumber(first.0.wrapping_sub(1)); + + assert!(second > first); + assert!(first < second); + assert!(third < first); + assert!(first > third); + assert!(second > third); + assert!(third < second); + } + } } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 2daeb9e8450..265730b3274 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -51,7 +51,7 @@ use matrix_sdk_common::locks::Mutex; use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use tracing::{info, instrument, trace, warn, Span}; +use tracing::{info, warn}; use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey}; use zeroize::Zeroize; @@ -79,6 +79,7 @@ mod traits; #[allow(missing_docs)] pub mod integration_tests; +use caches::{SequenceNumber, UsersForKeyQuery}; pub use error::{CryptoStoreError, Result}; pub use memorystore::MemoryStore; pub use traits::{CryptoStore, DynCryptoStore, IntoCryptoStore}; @@ -103,87 +104,6 @@ pub(crate) struct Store { tracked_users_loaded: Arc, } -/// Record of the users that are waiting for a /keys/query. -/// -/// To avoid races, we maintain a sequence number which is updated each time we -/// receive an invalidation notification. We also record the sequence number at -/// which each user was last invalidated. Then, we attach the current sequence -/// number to each `/keys/query` request, and when we get the response we can -/// tell if any users have been invalidated more recently than that request. -#[derive(Debug)] -struct UsersForKeyQuery { - /// The sequence number we will assign to the next addition to user_map - next_sequence_number: InvalidationSequenceNumber, - - /// The users pending a lookup, together with the sequence number at which - /// they were added to the list - user_map: HashMap, -} - -// We use wrapping arithmetic for the sequence numbers, to make sure we never -// run out of numbers. (2**64 should be enough for anyone, but it's easy enough -// just to make it wrap.) -// -// We use a *signed* counter so that we can compare values via a subtraction. -// For example, suppose we've just overflowed from i64::MAX to i64::MIN. -// (i64::MAX.wrapping_sub(i64::MIN)) is -1, which tells us that i64::MAX comes -// before i64::MIN in the sequence. -type InvalidationSequenceNumber = i64; - -impl UsersForKeyQuery { - /// Create a new, empty, `UsersForKeyQueryCache` - fn new() -> Self { - UsersForKeyQuery { next_sequence_number: 0, user_map: HashMap::new() } - } - - /// Record a new user that requires a key query - fn insert_user(&mut self, user: &UserId) { - let seq = self.next_sequence_number; - trace!(?user, sequence_number = seq, "Flagging user for key query"); - self.user_map.insert(user.to_owned(), seq); - self.next_sequence_number = self.next_sequence_number.wrapping_add(1); - } - - /// Record that a user has received an update with the given sequence - /// number. - /// - /// If the sequence number is newer than the oldest invalidation for this - /// user, it is removed from the list of those needing an update. - /// - /// Returns true if the user is now up-to-date, else false - #[instrument(level = "trace", skip(self), fields(invalidation_sequence))] - fn maybe_remove_user( - &mut self, - user: &UserId, - query_sequence: InvalidationSequenceNumber, - ) -> bool { - let last_invalidation = self.user_map.get(user); - - if let Some(invalidation_sequence) = last_invalidation { - Span::current().record("invalidation_sequence", invalidation_sequence); - if invalidation_sequence.wrapping_sub(query_sequence) > 0 { - trace!("User invalidated since this query started: still not up-to-date"); - false - } else { - trace!("User now up-to-date"); - self.user_map.remove(user); - true - } - } else { - trace!("User already up-to-date, nothing to do"); - true - } - } - - /// Fetch the list of users waiting for a key query, and the current - /// sequence number - fn users_for_key_query(&self) -> (HashSet, InvalidationSequenceNumber) { - // we return the sequence number of the last invalidation - let sequence_number = self.next_sequence_number.wrapping_sub(1); - (self.user_map.keys().cloned().collect(), sequence_number) - } -} - #[derive(Default, Debug)] #[allow(missing_docs)] pub struct Changes { @@ -760,7 +680,7 @@ impl Store { pub async fn mark_tracked_users_as_up_to_date( &self, users: impl Iterator, - sequence_number: InvalidationSequenceNumber, + sequence_number: SequenceNumber, ) -> Result<()> { let mut store_updates: Vec<(&UserId, bool)> = Vec::new(); let mut key_query_lock = self.users_for_key_query.lock().await; @@ -819,9 +739,7 @@ impl Store { /// A pair `(users, sequence_number)`, where `users` is the list of users to /// be queried, and `sequence_number` is the current sequence number, /// which should be returned in `mark_tracked_users_as_up_to_date`. - pub async fn users_for_key_query( - &self, - ) -> Result<(HashSet, InvalidationSequenceNumber)> { + pub async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { self.load_tracked_users().await?; Ok(self.users_for_key_query.lock().await.users_for_key_query()) From 786e9b5db97d5a77e13b4ce10b4ef92d064c2d5e Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Mon, 13 Mar 2023 15:33:18 +0100 Subject: [PATCH 10/28] Add createRoom UniFFI binding --- Cargo.lock | 14 +-- Cargo.toml | 4 +- bindings/matrix-sdk-ffi/src/api.udl | 36 ++++++++ bindings/matrix-sdk-ffi/src/client.rs | 119 +++++++++++++++++++++++++- 4 files changed, 160 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92e73a0a8a0..b91fe0a5183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4291,7 +4291,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.8.2" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "assign", "js_int", @@ -4305,7 +4305,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.8.1" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "js_int", "ruma-common", @@ -4316,7 +4316,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.16.2" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "assign", "bytes", @@ -4333,7 +4333,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.11.3" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "base64 0.21.0", "bytes", @@ -4366,7 +4366,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.7.1" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "js_int", "ruma-common", @@ -4377,7 +4377,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.1" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "js_int", "thiserror", @@ -4386,7 +4386,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.11.3" -source = "git+https://github.com/ruma/ruma?rev=543c03f8f2d0f5a357b6be41cc0e166aa58cce63#543c03f8f2d0f5a357b6be41cc0e166aa58cce63" +source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" dependencies = [ "once_cell", "proc-macro-crate", diff --git a/Cargo.toml b/Cargo.toml index e87e1db89ad..2dc8cdda59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ eyeball = "0.4.0" eyeball-im = "0.1.0" futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] } http = "0.2.6" -ruma = { git = "https://github.com/ruma/ruma", rev = "543c03f8f2d0f5a357b6be41cc0e166aa58cce63", features = ["client-api-c"] } -ruma-common = { git = "https://github.com/ruma/ruma", rev = "543c03f8f2d0f5a357b6be41cc0e166aa58cce63" } +ruma = { git = "https://github.com/ruma/ruma", rev = "03a58bcd883f2830c77aeafc95da789513f748b3", features = ["client-api-c"] } +ruma-common = { git = "https://github.com/ruma/ruma", rev = "03a58bcd883f2830c77aeafc95da789513f748b3" } once_cell = "1.16.0" serde = "1.0.151" serde_html_form = "0.2.0" diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 83304018304..4a067d5de28 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -149,6 +149,39 @@ interface SlidingSyncBuilder { SlidingSync build(); }; +dictionary CreateRoomParameters { + string name; + string? topic = null; + boolean is_encrypted; + boolean is_direct = false; + RoomVisibility visibility; + RoomPreset preset; + sequence? invite = null; + string? avatar = null; +}; + +enum RoomVisibility { + /// Indicates that the room will be shown in the published room list. + "Public", + + /// Indicates that the room will not be shown in the published room list. + "Private", +}; + +enum RoomPreset { + /// `join_rules` is set to `invite` and `history_visibility` is set to + /// `shared`. + "PrivateChat", + + /// `join_rules` is set to `public` and `history_visibility` is set to + /// `shared`. + "PublicChat", + + /// Same as `PrivateChat`, but all initial invitees get the same power level + /// as the creator. + "TrustedPrivateChat", +}; + interface Client { void set_delegate(ClientDelegate? delegate); @@ -179,6 +212,9 @@ interface Client { [Throws=ClientError] string device_id(); + [Throws=ClientError] + string create_room(CreateRoomParameters request); + [Throws=ClientError] string? account_data(string event_type); diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 77499e41338..fb951a7cf94 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -5,17 +5,25 @@ use matrix_sdk::{ media::{MediaFormat, MediaRequest, MediaThumbnailSize}, ruma::{ api::client::{ - account::whoami, error::ErrorKind, media::get_content_thumbnail::v3::Method, + account::whoami, + error::ErrorKind, + media::get_content_thumbnail::v3::Method, + room::{create_room, Visibility}, session::get_login_types, }, - events::{room::MediaSource, AnyToDeviceEvent}, + events::{ + room::{ + avatar::RoomAvatarEventContent, encryption::RoomEncryptionEventContent, MediaSource, + }, + AnyInitialStateEvent, AnyToDeviceEvent, InitialStateEvent, + }, serde::Raw, - TransactionId, UInt, + EventEncryptionAlgorithm, TransactionId, UInt, UserId, }, Client as MatrixClient, Error, LoopCtrl, }; use tokio::sync::broadcast::{self, error::RecvError}; -use tracing::{debug, warn}; +use tracing::{debug, error, warn}; use super::{room::Room, session_verification::SessionVerificationController, RUNTIME}; @@ -245,6 +253,15 @@ impl Client { Ok(device_id.to_string()) } + pub fn create_room(&self, request: CreateRoomParameters) -> anyhow::Result { + let client = self.client.clone(); + + RUNTIME.block_on(async move { + let response = client.create_room(request.into()).await?; + Ok(String::from(response.room_id())) + }) + } + /// Get the content of the event of the given type out of the account data /// store. /// @@ -386,6 +403,100 @@ impl Client { } } +pub struct CreateRoomParameters { + pub name: String, + pub topic: Option, + pub is_encrypted: bool, + pub is_direct: bool, + pub visibility: RoomVisibility, + pub preset: RoomPreset, + pub invite: Option>, + pub avatar: Option, +} + +impl From for create_room::v3::Request { + fn from(value: CreateRoomParameters) -> create_room::v3::Request { + let mut request = create_room::v3::Request::new(); + request.name = Some(value.name); + request.topic = value.topic; + request.is_direct = value.is_direct; + request.visibility = value.visibility.into(); + request.preset = Some(value.preset.into()); + request.invite = match value.invite { + Some(invite) => invite + .iter() + .filter_map(|user_id| match UserId::parse(user_id) { + Ok(id) => Some(id), + Err(e) => { + error!(user_id, "Skipping invalid user ID, error: {e}"); + None + } + }) + .collect(), + None => vec![], + }; + + let mut initial_state: Vec> = vec![]; + + if value.is_encrypted { + let content = + RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2); + initial_state.push(InitialStateEvent::new(content).to_raw_any()); + } + + if let Some(url) = value.avatar { + let mut content = RoomAvatarEventContent::new(); + content.url = Some(url.into()); + initial_state.push(InitialStateEvent::new(content).to_raw_any()); + } + + request.initial_state = initial_state; + + request + } +} + +pub enum RoomVisibility { + /// Indicates that the room will be shown in the published room list. + Public, + + /// Indicates that the room will not be shown in the published room list. + Private, +} + +impl From for Visibility { + fn from(value: RoomVisibility) -> Self { + match value { + RoomVisibility::Public => Self::Public, + RoomVisibility::Private => Self::Private, + } + } +} + +pub enum RoomPreset { + /// `join_rules` is set to `invite` and `history_visibility` is set to + /// `shared`. + PrivateChat, + + /// `join_rules` is set to `public` and `history_visibility` is set to + /// `shared`. + PublicChat, + + /// Same as `PrivateChat`, but all initial invitees get the same power level + /// as the creator. + TrustedPrivateChat, +} + +impl From for create_room::v3::RoomPreset { + fn from(value: RoomPreset) -> Self { + match value { + RoomPreset::PrivateChat => Self::PrivateChat, + RoomPreset::PublicChat => Self::PublicChat, + RoomPreset::TrustedPrivateChat => Self::TrustedPrivateChat, + } + } +} + pub struct Session { // Same fields as the Session type in matrix-sdk, just simpler types /// The access token used for this session. From 7dee9648cff1adcd9f328501570cc249a0b2119f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 15:00:24 +0100 Subject: [PATCH 11/28] sdk: Clone VerificationRequest twice in generate_qr_code method again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … required for the returned Future to be Send. --- crates/matrix-sdk-crypto/src/verification/requests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index e63bcd5e325..1709f5d2139 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -366,8 +366,7 @@ impl VerificationRequest { /// based verification. #[cfg(feature = "qrcode")] pub async fn generate_qr_code(&self) -> Result, CryptoStoreError> { - let inner = self.inner.read(); - + let inner = self.inner.get(); inner.generate_qr_code(self.we_started, self.inner.clone().into()).await } From e9058f58be2b7eac8555014ae163bea991a7ca55 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 14:18:25 +0100 Subject: [PATCH 12/28] Upgrade Ruma --- Cargo.lock | 14 +++++++------- Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b91fe0a5183..c4eb656732c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4291,7 +4291,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.8.2" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "assign", "js_int", @@ -4305,7 +4305,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.8.1" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "js_int", "ruma-common", @@ -4316,7 +4316,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.16.2" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "assign", "bytes", @@ -4333,7 +4333,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.11.3" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "base64 0.21.0", "bytes", @@ -4366,7 +4366,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.7.1" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "js_int", "ruma-common", @@ -4377,7 +4377,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.1" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "js_int", "thiserror", @@ -4386,7 +4386,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.11.3" -source = "git+https://github.com/ruma/ruma?rev=03a58bcd883f2830c77aeafc95da789513f748b3#03a58bcd883f2830c77aeafc95da789513f748b3" +source = "git+https://github.com/ruma/ruma?rev=a399a4017a9f0b249ce848a652f2e4fe65435eaf#a399a4017a9f0b249ce848a652f2e4fe65435eaf" dependencies = [ "once_cell", "proc-macro-crate", diff --git a/Cargo.toml b/Cargo.toml index 2dc8cdda59f..d7fe5b27e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ eyeball = "0.4.0" eyeball-im = "0.1.0" futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] } http = "0.2.6" -ruma = { git = "https://github.com/ruma/ruma", rev = "03a58bcd883f2830c77aeafc95da789513f748b3", features = ["client-api-c"] } -ruma-common = { git = "https://github.com/ruma/ruma", rev = "03a58bcd883f2830c77aeafc95da789513f748b3" } +ruma = { git = "https://github.com/ruma/ruma", rev = "a399a4017a9f0b249ce848a652f2e4fe65435eaf", features = ["client-api-c"] } +ruma-common = { git = "https://github.com/ruma/ruma", rev = "a399a4017a9f0b249ce848a652f2e4fe65435eaf" } once_cell = "1.16.0" serde = "1.0.151" serde_html_form = "0.2.0" From 59edc22a35c4ef162ea0a8cafccdf25e37ab1070 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 14:22:07 +0100 Subject: [PATCH 13/28] Use Action covenience methods Now Action::Coalesce will also generate a notification. --- crates/matrix-sdk-base/src/client.rs | 2 +- crates/matrix-sdk/src/room/timeline/inner.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 3d1be6b74d7..8be3af22433 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -411,7 +411,7 @@ impl BaseClient { if let Some(context) = &push_context { let actions = push_rules.get_actions(&event.event, context); - if actions.iter().any(|a| matches!(a, Action::Notify)) { + if actions.iter().any(Action::should_notify) { changes.add_notification( room_id, Notification::new( diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index 4daccfd2187..2c961b33389 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -35,7 +35,7 @@ use ruma::{ relation::Annotation, AnyMessageLikeEventContent, AnySyncTimelineEvent, }, - push::{Action, Tweak}, + push::Action, serde::Raw, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, TransactionId, UserId, @@ -691,8 +691,7 @@ async fn handle_remote_event( } else { Default::default() }; - let is_highlighted = - push_actions.iter().any(|a| matches!(a, Action::SetTweak(Tweak::Highlight(true)))); + let is_highlighted = push_actions.iter().any(Action::is_highlight); let event_meta = TimelineEventMetadata { sender, sender_profile, From 7121179b6ad3c5bb0d6e2a65dfff4908f93c1267 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 14:25:48 +0100 Subject: [PATCH 14/28] sdk: Make test code less repetitive --- .../matrix-sdk/src/room/timeline/tests/mod.rs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index ebba28a5341..d1970ddff58 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -31,7 +31,7 @@ use once_cell::sync::Lazy; use ruma::{ events::{ receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType}, - AnyMessageLikeEventContent, EmptyStateKey, MessageLikeEventContent, + AnyMessageLikeEventContent, AnySyncTimelineEvent, EmptyStateKey, MessageLikeEventContent, RedactedMessageLikeEventContent, RedactedStateEventContent, StateEventContent, StaticStateEventContent, }, @@ -80,8 +80,7 @@ impl TestTimeline { C: MessageLikeEventContent, { let ev = self.make_message_event(sender, content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_redacted_message_event(&self, sender: &UserId, content: C) @@ -89,8 +88,7 @@ impl TestTimeline { C: RedactedMessageLikeEventContent, { let ev = self.make_redacted_message_event(sender, content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_state_event(&self, sender: &UserId, content: C, prev_content: Option) @@ -98,8 +96,7 @@ impl TestTimeline { C: StaticStateEventContent, { let ev = self.make_state_event(sender, "", content, prev_content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_state_event_with_state_key( @@ -112,8 +109,7 @@ impl TestTimeline { C: StaticStateEventContent, { let ev = self.make_state_event(sender, state_key.as_ref(), content, prev_content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_redacted_state_event(&self, sender: &UserId, content: C) @@ -121,8 +117,7 @@ impl TestTimeline { C: RedactedStateEventContent, { let ev = self.make_redacted_state_event(sender, "", content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_redacted_state_event_with_state_key( @@ -134,13 +129,12 @@ impl TestTimeline { C: RedactedStateEventContent, { let ev = self.make_redacted_state_event(sender, state_key.as_ref(), content); - let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(Raw::new(&ev).unwrap().cast()).await; } async fn handle_live_custom_event(&self, event: JsonValue) { let raw = Raw::new(&event).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(raw).await; } async fn handle_live_redaction(&self, sender: &UserId, redacts: &EventId) { @@ -153,7 +147,11 @@ impl TestTimeline { "origin_server_ts": self.next_server_ts(), }); let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, Vec::default()).await; + self.handle_live_event(raw).await; + } + + async fn handle_live_event(&self, raw: Raw) { + self.inner.handle_live_event(raw, None, vec![]).await } async fn handle_local_event(&self, content: AnyMessageLikeEventContent) -> OwnedTransactionId { From ee6c0d9486640fb3d7a5442b1703da1b903577c2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 14:31:10 +0100 Subject: [PATCH 15/28] Simplify TimelineEvent construction --- .../src/deserialized_responses.rs | 19 +++++++++------ crates/matrix-sdk-crypto/src/machine.rs | 4 ++-- crates/matrix-sdk/src/room/common.rs | 24 +++++-------------- .../matrix-sdk/src/room/timeline/tests/mod.rs | 6 +---- crates/matrix-sdk/src/sliding_sync/room.rs | 10 ++++---- 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index b3d6ff29d9a..d8244e20ce7 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -86,8 +86,8 @@ impl From for SyncTimelineEvent { // this way, we simply cause the `room_id` field in the json to be // ignored by a subsequent deserialization. Self { - encryption_info: o.encryption_info, event: o.event.cast(), + encryption_info: o.encryption_info, push_actions: o.push_actions, } } @@ -104,6 +104,16 @@ pub struct TimelineEvent { pub push_actions: Vec, } +impl TimelineEvent { + /// Create a new `TimelineEvent` from the given raw event. + /// + /// This is a convenience constructor for when you don't need to set + /// `encryption_info` or `push_action`, for example inside a test. + pub fn new(event: Raw) -> Self { + Self { event, encryption_info: None, push_actions: vec![] } + } +} + #[cfg(test)] mod tests { use ruma::{ @@ -125,12 +135,7 @@ mod tests { "sender": "@carl:example.com", }); - let room_event = TimelineEvent { - event: Raw::new(&event).unwrap().cast(), - encryption_info: None, - push_actions: Vec::default(), - }; - + let room_event = TimelineEvent::new(Raw::new(&event).unwrap().cast()); let converted_room_event: SyncTimelineEvent = room_event.into(); let converted_event: AnySyncTimelineEvent = diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 8bb322fd0cb..f1c8766e1d5 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1179,9 +1179,9 @@ impl OlmMachine { let encryption_info = self.get_encryption_info(&session, &event.sender).await?; Ok(TimelineEvent { - encryption_info: Some(encryption_info), event: decrypted_event, - push_actions: Vec::default(), + encryption_info: Some(encryption_info), + push_actions: vec![], }) } else { Err(MegolmError::MissingRoomKey) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index c633ed4879b..f37f0c6fdd3 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -208,15 +208,7 @@ impl Common { start: http_response.start, end: http_response.end, #[cfg(not(feature = "e2e-encryption"))] - chunk: http_response - .chunk - .into_iter() - .map(|event| TimelineEvent { - event, - encryption_info: None, - push_actions: Vec::default(), - }) - .collect(), + chunk: http_response.chunk.into_iter().map(TimelineEvent::new).collect(), #[cfg(feature = "e2e-encryption")] chunk: Vec::with_capacity(http_response.chunk.len()), state: http_response.state, @@ -232,20 +224,16 @@ impl Common { if let Ok(event) = machine.decrypt_room_event(event.cast_ref(), room_id).await { event } else { - TimelineEvent { event, encryption_info: None, push_actions: Vec::default() } + TimelineEvent::new(event) } } else { - TimelineEvent { event, encryption_info: None, push_actions: Vec::default() } + TimelineEvent::new(event) }; response.chunk.push(decrypted_event); } } else { - response.chunk.extend(http_response.chunk.into_iter().map(|event| TimelineEvent { - event, - encryption_info: None, - push_actions: Vec::default(), - })); + response.chunk.extend(http_response.chunk.into_iter().map(TimelineEvent::new)); } Ok(response) @@ -294,11 +282,11 @@ impl Common { return Ok(event); } } - Ok(TimelineEvent { event, encryption_info: None, push_actions: Vec::default() }) + Ok(TimelineEvent::new(event)) } #[cfg(not(feature = "e2e-encryption"))] - Ok(TimelineEvent { event, encryption_info: None, push_actions: Vec::default() }) + Ok(TimelineEvent::new(event)) } pub(crate) async fn request_members(&self) -> Result> { diff --git a/crates/matrix-sdk/src/room/timeline/tests/mod.rs b/crates/matrix-sdk/src/room/timeline/tests/mod.rs index d1970ddff58..9105846afae 100644 --- a/crates/matrix-sdk/src/room/timeline/tests/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/tests/mod.rs @@ -161,11 +161,7 @@ impl TestTimeline { } async fn handle_back_paginated_custom_event(&self, event: JsonValue) { - let timeline_event = TimelineEvent { - event: Raw::new(&event).unwrap().cast(), - encryption_info: None, - push_actions: Vec::default(), - }; + let timeline_event = TimelineEvent::new(Raw::new(&event).unwrap().cast()); self.inner.handle_back_paginated_event(timeline_event).await; } diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 1abaa7dd8e7..48d270ba234 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -368,8 +368,8 @@ mod tests { room_id: <&RoomId>::try_from("!29fhd83h92h0:example.com").unwrap().to_owned(), inner: v4::SlidingSyncRoom::default(), prev_batch: Some("let it go!".to_owned()), - timeline_queue: vector![TimelineEvent { - event: Raw::new(&json! ({ + timeline_queue: vector![TimelineEvent::new( + Raw::new(&json! ({ "content": RoomMessageEventContent::text_plain("let it gooo!"), "type": "m.room.message", "event_id": "$xxxxx:example.org", @@ -378,10 +378,8 @@ mod tests { "sender": "@bob:example.com", })) .unwrap() - .cast(), - encryption_info: None, - push_actions: Vec::default(), - } + .cast() + ) .into()], }; From 0f7d839c492fb7823e8a0107dc3a12316b7b4209 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 13 Mar 2023 16:27:59 +0100 Subject: [PATCH 16/28] Undo room_type => room_state rename in serialized form of RoomInfo --- crates/matrix-sdk-base/src/rooms/normal.rs | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 2db39edea30..3b2fb92ad4c 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -515,6 +515,7 @@ pub struct RoomInfo { /// The unique room id of the room. pub(crate) room_id: Arc, /// The state of the room. + #[serde(rename = "room_type")] // for backwards compatibility room_state: RoomState, /// The unread notifications counts. notification_counts: UnreadNotificationsCount, @@ -820,6 +821,65 @@ mod test { MinimalStateEvent, OriginalMinimalStateEvent, }; + #[test] + fn room_info_serialization() { + // This test exists to make sure we don't accidentally change the + // serialized format for `RoomInfo`. + + let info = RoomInfo { + room_id: room_id!("!gda78o:server.tld").into(), + room_state: RoomState::Invited, + notification_counts: UnreadNotificationsCount { + highlight_count: 1, + notification_count: 2, + }, + summary: RoomSummary { + heroes: vec!["Somebody".to_owned()], + joined_member_count: 5, + invited_member_count: 0, + }, + members_synced: true, + last_prev_batch: Some("pb".to_owned()), + sync_info: SyncInfo::FullySynced, + encryption_state_synced: true, + base_info: BaseRoomInfo::new(), + }; + + let info_json = json!({ + "room_id": "!gda78o:server.tld", + "room_type": "Invited", + "notification_counts": { + "highlight_count": 1, + "notification_count": 2, + }, + "summary": { + "heroes": ["Somebody"], + "joined_member_count": 5, + "invited_member_count": 0, + }, + "members_synced": true, + "last_prev_batch": "pb", + "sync_info": "FullySynced", + "encryption_state_synced": true, + "base_info": { + "avatar": null, + "canonical_alias": null, + "create": null, + "dm_targets": [], + "encryption": null, + "guest_access": null, + "history_visibility": null, + "join_rules": null, + "max_power_level": 100, + "name": null, + "tombstone": null, + "topic": null, + } + }); + + assert_eq!(serde_json::to_value(info).unwrap(), info_json); + } + fn make_room(room_type: RoomState) -> (Arc, Room) { let store = Arc::new(MemoryStore::new()); let user_id = user_id!("@me:example.org"); From b4b111f91f535068689965a4f52d50aaf1c09da6 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 6 Feb 2023 12:17:44 +0000 Subject: [PATCH 17/28] Store encryption settings --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 50 ++++++++++--- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 74 ++++++++++++++++--- crates/matrix-sdk-base/src/client.rs | 3 +- crates/matrix-sdk-crypto/src/machine.rs | 59 ++++++++++++++- .../src/session_manager/group_sessions.rs | 30 +++++++- .../src/store/memorystore.rs | 2 +- .../migrations/002_encryption_settings.sql | 4 + 7 files changed, 197 insertions(+), 25 deletions(-) create mode 100644 crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 3903289acb4..2c03a98301a 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -500,12 +500,18 @@ pub enum EventEncryptionAlgorithm { impl From for RustEventEncryptionAlgorithm { fn from(a: EventEncryptionAlgorithm) -> Self { match a { - EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => { - RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 - } - EventEncryptionAlgorithm::MegolmV1AesSha2 => { - RustEventEncryptionAlgorithm::MegolmV1AesSha2 - } + EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + EventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, + } + } +} + +impl From for EventEncryptionAlgorithm { + fn from(value: RustEventEncryptionAlgorithm) -> Self { + match value { + RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, + RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, + _ => todo!(), } } } @@ -540,10 +546,22 @@ pub enum HistoryVisibility { impl From for RustHistoryVisibility { fn from(h: HistoryVisibility) -> Self { match h { - HistoryVisibility::Invited => RustHistoryVisibility::Invited, - HistoryVisibility::Joined => RustHistoryVisibility::Joined, - HistoryVisibility::Shared => RustHistoryVisibility::Shared, - HistoryVisibility::WorldReadable => RustHistoryVisibility::Shared, + HistoryVisibility::Invited => Self::Invited, + HistoryVisibility::Joined => Self::Joined, + HistoryVisibility::Shared => Self::Shared, + HistoryVisibility::WorldReadable => Self::Shared, + } + } +} + +impl From for HistoryVisibility { + fn from(h: RustHistoryVisibility) -> Self { + match h { + RustHistoryVisibility::Invited => Self::Invited, + RustHistoryVisibility::Joined => Self::Joined, + RustHistoryVisibility::Shared => Self::Shared, + RustHistoryVisibility::WorldReadable => Self::WorldReadable, + _ => todo!(), } } } @@ -583,6 +601,18 @@ impl From for RustEncryptionSettings { } } +impl From for EncryptionSettings { + fn from(value: RustEncryptionSettings) -> Self { + EncryptionSettings { + algorithm: value.algorithm.into(), + rotation_period: value.rotation_period.as_secs(), + rotation_period_msgs: value.rotation_period_msgs, + history_visibility: value.history_visibility.into(), + only_allow_trusted_devices: value.only_allow_trusted_devices, + } + } +} + /// An event that was successfully decrypted. pub struct DecryptedEvent { /// The decrypted version of the event. diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 814bf021a8f..1cd4b9fe4dd 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -53,9 +53,9 @@ use crate::{ responses::{response_from_string, OwnedResponse}, BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings, - KeyImportError, KeysImportResult, MegolmV1BackupKey, ProgressListener, Request, RequestType, - RequestVerificationResult, RoomKeyCounts, Sas, SignatureUploadRequest, StartSasResult, - UserIdentity, Verification, VerificationRequest, + EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey, + ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, Sas, + SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, }; /// A high level state machine that handles E2EE for Matrix. @@ -563,6 +563,65 @@ impl OlmMachine { .map(|r| r.into())) } + /// TODO: docs + pub fn get_encryption_settings( + &self, + room_id: &str, + ) -> Result, CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + let settings = + self.runtime.block_on(self.inner.get_encryption_settings(&room_id))?.map(|v| v.into()); + Ok(settings) + } + + /// TODO: docs + pub fn set_event_encryption_algorithm( + &self, + room_id: &str, + algorithm: EventEncryptionAlgorithm, + ) -> Result<(), CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + self.runtime.block_on(async move { + let mut settings = + self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + settings.algorithm = algorithm.into(); + self.inner.set_encryption_settings(&room_id, settings).await?; + Ok(()) + }) + } + + /// TODO: docs + pub fn set_block_untrusted_devices_per_room( + &self, + room_id: &str, + block_untrusted_devices: bool, + ) -> Result<(), CryptoStoreError> { + let room_id = RoomId::parse(room_id)?; + self.runtime.block_on(async move { + let mut settings = + self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + settings.only_allow_trusted_devices = block_untrusted_devices; + self.inner.set_encryption_settings(&room_id, settings).await?; + Ok(()) + }) + } + + /// TODO: docs + pub fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> Result<(), CryptoStoreError> { + self.runtime + .block_on(self.inner.set_block_untrusted_devices_globally(block_untrusted_devices))?; + Ok(()) + } + + /// TODO: docs + pub fn is_blocking_untrusted_devices_globally(&self) -> Result { + let block = self.runtime.block_on(self.inner.get_block_untrusted_devices_globally())?; + Ok(block) + } + /// Share a room key with the given list of users for the given room. /// /// After the request was sent out and a successful response was received @@ -586,17 +645,14 @@ impl OlmMachine { &self, room_id: String, users: Vec, - settings: EncryptionSettings, ) -> Result, CryptoStoreError> { let users: Vec = users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self.runtime.block_on(self.inner.share_room_key( - &room_id, - users.iter().map(Deref::deref), - settings, - ))?; + let requests = self + .runtime + .block_on(self.inner.share_room_key(&room_id, users.iter().map(Deref::deref)))?; Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 8be3af22433..2450c9d3da7 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1075,7 +1075,8 @@ impl BaseClient { let settings = settings.ok_or(Error::EncryptionNotEnabled)?; let settings = EncryptionSettings::new(settings, history_visibility, false); - Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?) + Ok(o.share_room_key_with_settings(room_id, members.map(Deref::deref), settings) + .await?) } None => panic!("Olm machine wasn't started"), } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index f1c8766e1d5..f897fbf9af7 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, + collections::{BTreeMap, BTreeSet, HashMap, HashSet}, sync::Arc, time::Duration, }; @@ -510,6 +510,50 @@ impl OlmMachine { self.session_manager.get_missing_sessions(users).await } + /// TODO: docs + pub async fn get_encryption_settings( + &self, + room_id: &RoomId, + ) -> StoreResult> { + let settings = self.store.load_encryption_settings(room_id).await?; + Ok(settings) + } + + /// TODO: docs + pub async fn set_encryption_settings( + &self, + room_id: &RoomId, + settings: EncryptionSettings, + ) -> StoreResult<()> { + self.store + .save_changes(Changes { + encryption_settings: HashMap::from([(room_id.into(), settings)]), + ..Default::default() + }) + .await?; + Ok(()) + } + + /// TODO: docs + pub async fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> StoreResult<()> { + self.store + .save_changes(Changes { + block_untrusted_devices_globally: Some(block_untrusted_devices), + ..Default::default() + }) + .await?; + Ok(()) + } + + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { + let block = self.store.block_untrusted_devices_globally().await?; + Ok(block) + } + /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// @@ -754,9 +798,18 @@ impl OlmMachine { &self, room_id: &RoomId, users: impl Iterator, - encryption_settings: impl Into, ) -> OlmResult>> { - self.group_session_manager.share_room_key(room_id, users, encryption_settings).await + self.group_session_manager.share_room_key(room_id, users).await + } + + /// TODO: docs + pub async fn share_room_key_with_settings( + &self, + room_id: &RoomId, + users: impl Iterator, + settings: impl Into, + ) -> OlmResult>> { + self.group_session_manager.share_room_key_with_settings(room_id, users, settings).await } /// Receive an unencrypted verification event. diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index af8783a67d4..002612e0387 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,6 +441,34 @@ impl GroupSessionManager { self.sessions.clone() } + pub async fn share_room_key( + &self, + room_id: &RoomId, + users: impl Iterator, + ) -> OlmResult>> { + // Load settings from store or create new + let mut encryption_settings = + if let Some(settings) = self.store.load_encryption_settings(room_id).await? { + settings + } else { + let settings = EncryptionSettings::default(); + let changes = Changes { + encryption_settings: HashMap::from([(room_id.into(), settings.clone())]), + ..Default::default() + }; + self.store.save_changes(changes).await?; + settings + }; + + // Combine per-room and global unverified devices block + if !encryption_settings.only_allow_trusted_devices { + encryption_settings.only_allow_trusted_devices = + self.store.block_untrusted_devices_globally().await?; + } + + self.share_room_key_with_settings(room_id, users, encryption_settings).await + } + /// Get to-device requests to share a room key with users in a room. /// /// # Arguments @@ -451,7 +479,7 @@ impl GroupSessionManager { /// /// `encryption_settings` - The settings that should be used for /// the room key. - pub async fn share_room_key( + pub async fn share_room_key_with_settings( &self, room_id: &RoomId, users: impl Iterator, diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 87b3e39f891..12de050448c 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -29,7 +29,7 @@ use crate::{ gossiping::{GossipRequest, SecretInfo}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, - TrackedUser, + EncryptionSettings, TrackedUser, }; fn encode_key_info(info: &SecretInfo) -> String { diff --git a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql new file mode 100644 index 00000000000..ca6382280d1 --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql @@ -0,0 +1,4 @@ +CREATE TABLE "encryption_settings"( + "room_id" BLOB PRIMARY KEY NOT NULL, + "data" BLOB NOT NULL +); From 7e7c91699faa3c87c0f3e14a7f01223e399b84ea Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 24 Feb 2023 15:10:04 +0000 Subject: [PATCH 18/28] Generic key-value API --- Cargo.lock | 1 + crates/matrix-sdk-crypto/Cargo.toml | 1 + crates/matrix-sdk-crypto/src/machine.rs | 34 +++++++------------ .../src/session_manager/group_sessions.rs | 26 +++++++------- .../src/store/memorystore.rs | 10 +++++- crates/matrix-sdk-crypto/src/store/mod.rs | 30 +++++++++++++++- crates/matrix-sdk-crypto/src/store/traits.rs | 24 +++++++++++++ .../migrations/002_encryption_settings.sql | 4 --- crates/matrix-sdk-sqlite/src/crypto_store.rs | 26 ++++++++++++++ 9 files changed, 115 insertions(+), 41 deletions(-) delete mode 100644 crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql diff --git a/Cargo.lock b/Cargo.lock index c4eb656732c..c9ae8743fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,6 +2753,7 @@ dependencies = [ "pbkdf2", "proptest", "rand 0.8.5", + "rmp-serde", "ruma", "serde", "serde_json", diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index f8e8d0d747d..c787ca3c475 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -47,6 +47,7 @@ pbkdf2 = { version = "0.11.0", default-features = false } rand = "0.8.5" ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc2677"] } serde = { workspace = true, features = ["derive", "rc"] } +rmp-serde = "1.1.1" serde_json = { workspace = true } sha2 = "0.10.2" thiserror = { workspace = true } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index f897fbf9af7..02845628697 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::{ - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, + collections::{BTreeMap, BTreeSet, HashSet}, sync::Arc, time::Duration, }; @@ -515,8 +515,9 @@ impl OlmMachine { &self, room_id: &RoomId, ) -> StoreResult> { - let settings = self.store.load_encryption_settings(room_id).await?; - Ok(settings) + let key = format!("encryption_settings-{room_id}"); + let value = self.store.get_value(&key).await?; + Ok(value) } /// TODO: docs @@ -525,35 +526,26 @@ impl OlmMachine { room_id: &RoomId, settings: EncryptionSettings, ) -> StoreResult<()> { - self.store - .save_changes(Changes { - encryption_settings: HashMap::from([(room_id.into(), settings)]), - ..Default::default() - }) - .await?; + let key = format!("encryption_settings-{room_id}"); + self.store.set_value(&key, &settings).await?; Ok(()) } + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { + let value = self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); + Ok(value) + } + /// TODO: docs pub async fn set_block_untrusted_devices_globally( &self, block_untrusted_devices: bool, ) -> StoreResult<()> { - self.store - .save_changes(Changes { - block_untrusted_devices_globally: Some(block_untrusted_devices), - ..Default::default() - }) - .await?; + self.store.set_value("block_untrusted_devices", &block_untrusted_devices).await?; Ok(()) } - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { - let block = self.store.block_untrusted_devices_globally().await?; - Ok(block) - } - /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 002612e0387..676cf3acfb7 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,29 +441,27 @@ impl GroupSessionManager { self.sessions.clone() } + /// TODO: Docs pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, ) -> OlmResult>> { // Load settings from store or create new - let mut encryption_settings = - if let Some(settings) = self.store.load_encryption_settings(room_id).await? { - settings - } else { - let settings = EncryptionSettings::default(); - let changes = Changes { - encryption_settings: HashMap::from([(room_id.into(), settings.clone())]), - ..Default::default() - }; - self.store.save_changes(changes).await?; - settings - }; + let key = format!("encryption_settings-{room_id}"); + let mut encryption_settings = if let Some(settings) = self.store.get_value(&key).await? { + settings + } else { + let settings = EncryptionSettings::default(); + self.store.set_value(&key, &settings).await?; + settings + }; // Combine per-room and global unverified devices block if !encryption_settings.only_allow_trusted_devices { - encryption_settings.only_allow_trusted_devices = - self.store.block_untrusted_devices_globally().await?; + let block_globally = + self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); + encryption_settings.only_allow_trusted_devices = block_globally } self.share_room_key_with_settings(room_id, users, encryption_settings).await diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 12de050448c..6ef56f9a42e 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -29,7 +29,7 @@ use crate::{ gossiping::{GossipRequest, SecretInfo}, identities::{ReadOnlyDevice, ReadOnlyUserIdentities}, olm::{OutboundGroupSession, PrivateCrossSigningIdentity}, - EncryptionSettings, TrackedUser, + TrackedUser, }; fn encode_key_info(info: &SecretInfo) -> String { @@ -270,6 +270,14 @@ impl CryptoStore for MemoryStore { async fn load_backup_keys(&self) -> Result { Ok(BackupKeys::default()) } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + Ok(()) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 265730b3274..5eac5a8dd59 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -49,7 +49,7 @@ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId}; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; use tracing::{info, warn}; use vodozemac::{megolm::SessionOrdering, Curve25519PublicKey}; @@ -751,6 +751,34 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } + + /// TODO: docs + pub async fn get_value(&self, key: &str) -> Result> { + let Some(value) = self.get_custom_value(key).await? else { + return Ok(None); + }; + let deserialized = self.deserialize_value(&value)?; + Ok(Some(deserialized)) + } + + /// TODO: docs + pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> { + let serialized = self.serialize_value(value)?; + self.set_custom_value(key, serialized).await?; + Ok(()) + } + + fn serialize_value(&self, value: &impl Serialize) -> Result> { + let serialized = + rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; // TODO: Serialization type + Ok(serialized) + } + + fn deserialize_value(&self, value: &[u8]) -> Result { + let deserialized = + rmp_serde::from_slice(value).map_err(|e| CryptoStoreError::Backend(e.into()))?; + Ok(deserialized) + } } impl Deref for Store { diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index 690abe2179f..e58ea79e2c6 100644 --- a/crates/matrix-sdk-crypto/src/store/traits.rs +++ b/crates/matrix-sdk-crypto/src/store/traits.rs @@ -186,6 +186,22 @@ pub trait CryptoStore: AsyncTraitDeps { &self, request_id: &TransactionId, ) -> Result<(), Self::Error>; + + /// Get arbitrary data from the store + /// + /// # Arguments + /// + /// * `key` - The key to fetch data for + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error>; + + /// Put arbitrary data into the store + /// + /// # Arguments + /// + /// * `key` - The key to insert data into + /// + /// * `value` - The value to insert + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<(), Self::Error>; } #[repr(transparent)] @@ -312,6 +328,14 @@ impl CryptoStore for EraseCryptoStoreError { async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()> { self.0.delete_outgoing_secret_requests(request_id).await.map_err(Into::into) } + + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error> { + self.0.get_custom_value(key).await.map_err(Into::into) + } + + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<(), Self::Error> { + self.0.set_custom_value(key, value).await.map_err(Into::into) + } } /// A type-erased [`CryptoStore`]. diff --git a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql b/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql deleted file mode 100644 index ca6382280d1..00000000000 --- a/crates/matrix-sdk-sqlite/migrations/002_encryption_settings.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE "encryption_settings"( - "room_id" BLOB PRIMARY KEY NOT NULL, - "data" BLOB NOT NULL -); diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 2b5523c1899..1c90b44b4b1 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -946,6 +946,32 @@ impl CryptoStore for SqliteCryptoStore { let request_id = self.encode_key("key_requests", request_id.as_bytes()); Ok(self.acquire().await?.delete_key_request(request_id).await?) } + + async fn get_custom_value(&self, key: &str) -> Result>> { + let Some(serialized) = self.acquire().await?.get_kv(key).await? else { + return Ok(None); + }; + let value = if let Some(cipher) = &self.store_cipher { + let encrypted = rmp_serde::from_slice(&serialized)?; + cipher.decrypt_value_data(encrypted)? + } else { + serialized + }; + + Ok(Some(value)) + } + + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + let serialized = if let Some(cipher) = &self.store_cipher { + let encrypted = cipher.encrypt_value_data(value)?; + rmp_serde::to_vec_named(&encrypted)? + } else { + value + }; + + self.acquire().await?.set_kv(key, serialized).await?; + Ok(()) + } } #[cfg(test)] From 33b348c3760092b08ed78cd81b9c6628a24558da Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 27 Feb 2023 16:45:56 +0000 Subject: [PATCH 19/28] Constrain new api to Store struct --- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 52 ++++++++++++----- crates/matrix-sdk-base/src/client.rs | 3 +- crates/matrix-sdk-crypto/src/machine.rs | 56 +++---------------- .../src/session_manager/group_sessions.rs | 28 +--------- crates/matrix-sdk-crypto/src/store/mod.rs | 41 +++++++++++++- 5 files changed, 87 insertions(+), 93 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 1cd4b9fe4dd..8e6051d533e 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -569,8 +569,10 @@ impl OlmMachine { room_id: &str, ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; - let settings = - self.runtime.block_on(self.inner.get_encryption_settings(&room_id))?.map(|v| v.into()); + let settings = self + .runtime + .block_on(self.inner.store().get_encryption_settings(&room_id))? + .map(|v| v.into()); Ok(settings) } @@ -583,9 +585,9 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); settings.algorithm = algorithm.into(); - self.inner.set_encryption_settings(&room_id, settings).await?; + self.inner.store().set_encryption_settings(&room_id, settings).await?; Ok(()) }) } @@ -599,9 +601,9 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); settings.only_allow_trusted_devices = block_untrusted_devices; - self.inner.set_encryption_settings(&room_id, settings).await?; + self.inner.store().set_encryption_settings(&room_id, settings).await?; Ok(()) }) } @@ -611,14 +613,16 @@ impl OlmMachine { &self, block_untrusted_devices: bool, ) -> Result<(), CryptoStoreError> { - self.runtime - .block_on(self.inner.set_block_untrusted_devices_globally(block_untrusted_devices))?; + self.runtime.block_on( + self.inner.store().set_block_untrusted_devices_globally(block_untrusted_devices), + )?; Ok(()) } /// TODO: docs pub fn is_blocking_untrusted_devices_globally(&self) -> Result { - let block = self.runtime.block_on(self.inner.get_block_untrusted_devices_globally())?; + let block = + self.runtime.block_on(self.inner.store().get_block_untrusted_devices_globally())?; Ok(block) } @@ -639,8 +643,6 @@ impl OlmMachine { /// /// * `users` - The list of users which are considered to be members of the /// room and should receive the room key. - /// - /// * `settings` - The settings that should be used for the room key. pub fn share_room_key( &self, room_id: String, @@ -650,9 +652,31 @@ impl OlmMachine { users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self - .runtime - .block_on(self.inner.share_room_key(&room_id, users.iter().map(Deref::deref)))?; + let requests = self.runtime.block_on(async move { + // Load settings from store or create new + let mut encryption_settings = if let Some(settings) = + self.inner.store().get_encryption_settings(room_id.into()).await? + { + settings + } else { + let settings = EncryptionSettings::default(); + self.inner.store().set_encryption_settings(room_id.into(), settings).await?; + settings + }; + + // Combine per-room and global unverified devices block + if !encryption_settings.only_allow_trusted_devices { + let block_globally = self + .inner + .store() + .get_block_untrusted_devices_globally() + .await? + .unwrap_or_default(); + encryption_settings.only_allow_trusted_devices = block_globally + } + + self.inner.share_room_key(&room_id, users.iter().map(Deref::deref), settings).await + }); Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 2450c9d3da7..8be3af22433 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -1075,8 +1075,7 @@ impl BaseClient { let settings = settings.ok_or(Error::EncryptionNotEnabled)?; let settings = EncryptionSettings::new(settings, history_visibility, false); - Ok(o.share_room_key_with_settings(room_id, members.map(Deref::deref), settings) - .await?) + Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?) } None => panic!("Olm machine wasn't started"), } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 02845628697..cd1dbd1745c 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -296,6 +296,11 @@ impl OlmMachine { Ok(OlmMachine::new_helper(user_id, device_id, store, account, identity)) } + /// TODO: docs + pub fn store(&self) -> &Store { + &self.store + } + /// The unique user id that owns this `OlmMachine` instance. pub fn user_id(&self) -> &UserId { &self.user_id @@ -510,42 +515,6 @@ impl OlmMachine { self.session_manager.get_missing_sessions(users).await } - /// TODO: docs - pub async fn get_encryption_settings( - &self, - room_id: &RoomId, - ) -> StoreResult> { - let key = format!("encryption_settings-{room_id}"); - let value = self.store.get_value(&key).await?; - Ok(value) - } - - /// TODO: docs - pub async fn set_encryption_settings( - &self, - room_id: &RoomId, - settings: EncryptionSettings, - ) -> StoreResult<()> { - let key = format!("encryption_settings-{room_id}"); - self.store.set_value(&key, &settings).await?; - Ok(()) - } - - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> StoreResult { - let value = self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); - Ok(value) - } - - /// TODO: docs - pub async fn set_block_untrusted_devices_globally( - &self, - block_untrusted_devices: bool, - ) -> StoreResult<()> { - self.store.set_value("block_untrusted_devices", &block_untrusted_devices).await?; - Ok(()) - } - /// Receive a successful key claim response and create new Olm sessions with /// the claimed keys. /// @@ -786,22 +755,15 @@ impl OlmMachine { /// used. /// /// `users` - The list of users that should receive the room key. + /// + /// `settings` - TODO: pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, + encryption_settings: impl Into, ) -> OlmResult>> { - self.group_session_manager.share_room_key(room_id, users).await - } - - /// TODO: docs - pub async fn share_room_key_with_settings( - &self, - room_id: &RoomId, - users: impl Iterator, - settings: impl Into, - ) -> OlmResult>> { - self.group_session_manager.share_room_key_with_settings(room_id, users, settings).await + self.group_session_manager.share_room_key(room_id, users, encryption_settings).await } /// Receive an unencrypted verification event. diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 676cf3acfb7..af8783a67d4 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -441,32 +441,6 @@ impl GroupSessionManager { self.sessions.clone() } - /// TODO: Docs - pub async fn share_room_key( - &self, - room_id: &RoomId, - users: impl Iterator, - ) -> OlmResult>> { - // Load settings from store or create new - let key = format!("encryption_settings-{room_id}"); - let mut encryption_settings = if let Some(settings) = self.store.get_value(&key).await? { - settings - } else { - let settings = EncryptionSettings::default(); - self.store.set_value(&key, &settings).await?; - settings - }; - - // Combine per-room and global unverified devices block - if !encryption_settings.only_allow_trusted_devices { - let block_globally = - self.store.get_value("block_untrusted_devices").await?.unwrap_or_default(); - encryption_settings.only_allow_trusted_devices = block_globally - } - - self.share_room_key_with_settings(room_id, users, encryption_settings).await - } - /// Get to-device requests to share a room key with users in a room. /// /// # Arguments @@ -477,7 +451,7 @@ impl GroupSessionManager { /// /// `encryption_settings` - The settings that should be used for /// the room key. - pub async fn share_room_key_with_settings( + pub async fn share_room_key( &self, room_id: &RoomId, users: impl Iterator, diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 5eac5a8dd59..2585ad46ef7 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -48,7 +48,9 @@ use std::{ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; -use ruma::{events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, UserId}; +use ruma::{ + events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UserId, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; use tracing::{info, warn}; @@ -66,7 +68,7 @@ use crate::{ }, utilities::encode, verification::VerificationMachine, - CrossSigningStatus, + CrossSigningStatus, EncryptionSettings, }; pub mod caches; @@ -93,7 +95,7 @@ pub use crate::gossiping::{GossipRequest, SecretInfo}; /// generics don't mix let the CryptoStore store strings and this wrapper /// adds the generic interface on top. #[derive(Debug, Clone)] -pub(crate) struct Store { +pub struct Store { user_id: Arc, identity: Arc>, inner: Arc, @@ -752,6 +754,39 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } + /// TODO: docs + pub async fn get_encryption_settings( + &self, + room_id: &RoomId, + ) -> Result> { + let key = format!("encryption_settings-{room_id}"); + self.get_value(&key).await + } + + /// TODO: docs + pub async fn set_encryption_settings( + &self, + room_id: &RoomId, + settings: EncryptionSettings, + ) -> Result<()> { + let key = format!("encryption_settings-{room_id}"); + self.set_value(&key, &settings).await + } + + /// TODO: docs + pub async fn get_block_untrusted_devices_globally(&self) -> Result { + let value = self.get_value("block_untrusted_devices").await?.unwrap_or_default(); + Ok(value) + } + + /// TODO: docs + pub async fn set_block_untrusted_devices_globally( + &self, + block_untrusted_devices: bool, + ) -> Result<()> { + self.set_value("block_untrusted_devices", &block_untrusted_devices).await + } + /// TODO: docs pub async fn get_value(&self, key: &str) -> Result> { let Some(value) = self.get_custom_value(key).await? else { From 283137f190fd36c468da6043296efb0dc08b4bad Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 28 Feb 2023 09:50:40 +0000 Subject: [PATCH 20/28] Introduce RoomSettings --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 66 +++++----- bindings/matrix-sdk-crypto-ffi/src/logger.rs | 10 +- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 122 ++++++++++-------- crates/matrix-sdk-crypto/src/machine.rs | 5 +- .../src/store/integration_tests.rs | 56 +++++++- .../src/store/memorystore.rs | 11 +- crates/matrix-sdk-crypto/src/store/mod.rs | 64 ++++----- crates/matrix-sdk-crypto/src/store/traits.rs | 17 ++- .../matrix-sdk-indexeddb/src/crypto_store.rs | 17 +++ crates/matrix-sdk-sled/src/crypto_store.rs | 19 ++- .../migrations/003_room_settings.sql | 4 + crates/matrix-sdk-sqlite/src/crypto_store.rs | 49 ++++++- 12 files changed, 314 insertions(+), 126 deletions(-) create mode 100644 crates/matrix-sdk-sqlite/migrations/003_room_settings.sql diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 2c03a98301a..bc8759ef3a8 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -33,6 +33,7 @@ use matrix_sdk_crypto::{ backups::SignatureState, olm::{IdentityKeys, InboundGroupSession, Session}, store::{Changes, CryptoStore}, + store::RoomSettings as RustRoomSettings, types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey}, EncryptionSettings as RustEncryptionSettings, LocalTrust, }; @@ -506,12 +507,16 @@ impl From for RustEventEncryptionAlgorithm { } } -impl From for EventEncryptionAlgorithm { - fn from(value: RustEventEncryptionAlgorithm) -> Self { +impl TryFrom for EventEncryptionAlgorithm { + type Error = String; + + fn try_from(value: RustEventEncryptionAlgorithm) -> Result { match value { - RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2, - RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Self::MegolmV1AesSha2, - _ => todo!(), + RustEventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => { + Ok(Self::OlmV1Curve25519AesSha2) + } + RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2), + _ => Err(format!("Unsupported algorithm {value}")), } } } @@ -554,18 +559,6 @@ impl From for RustHistoryVisibility { } } -impl From for HistoryVisibility { - fn from(h: RustHistoryVisibility) -> Self { - match h { - RustHistoryVisibility::Invited => Self::Invited, - RustHistoryVisibility::Joined => Self::Joined, - RustHistoryVisibility::Shared => Self::Shared, - RustHistoryVisibility::WorldReadable => Self::WorldReadable, - _ => todo!(), - } - } -} - /// Settings that should be used when a room key is shared. /// /// These settings control which algorithm the room key should use, how long a @@ -601,18 +594,6 @@ impl From for RustEncryptionSettings { } } -impl From for EncryptionSettings { - fn from(value: RustEncryptionSettings) -> Self { - EncryptionSettings { - algorithm: value.algorithm.into(), - rotation_period: value.rotation_period.as_secs(), - rotation_period_msgs: value.rotation_period_msgs, - history_visibility: value.history_visibility.into(), - only_allow_trusted_devices: value.only_allow_trusted_devices, - } - } -} - /// An event that was successfully decrypted. pub struct DecryptedEvent { /// The decrypted version of the event. @@ -739,6 +720,33 @@ impl From for CrossSigningStatus { } } +/// Room encryption settings which are modified by state events or user options +pub struct RoomSettings { + /// The encryption algorithm that should be used in the room. + pub algorithm: EventEncryptionAlgorithm, + /// Should untrusted devices receive the room key, or should they be + /// excluded from the conversation. + pub only_allow_trusted_devices: bool, +} + +impl TryFrom for RoomSettings { + type Error = String; + + fn try_from(value: RustRoomSettings) -> Result { + let algorithm = value.algorithm.try_into()?; + Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices }) + } +} + +impl From for RustRoomSettings { + fn from(value: RoomSettings) -> Self { + Self { + algorithm: value.algorithm.into(), + only_allow_trusted_devices: value.only_allow_trusted_devices, + } + } +} + fn parse_user_id(user_id: &str) -> Result { ruma::UserId::parse(user_id).map_err(|e| CryptoStoreError::InvalidUserId(user_id.to_owned(), e)) } diff --git a/bindings/matrix-sdk-crypto-ffi/src/logger.rs b/bindings/matrix-sdk-crypto-ffi/src/logger.rs index ce9ea068741..c2c99111953 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/logger.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/logger.rs @@ -44,9 +44,13 @@ pub struct LoggerWrapper { pub fn set_logger(logger: Box) { let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) }; - let filter = EnvFilter::from_default_env().add_directive( - "matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"), - ); + let filter = EnvFilter::from_default_env() + .add_directive( + "matrix_sdk_crypto=trace".parse().expect("Can't parse logging filter directive"), + ) + .add_directive( + "matrix_sdk_sqlite=debug".parse().expect("Can't parse logging filter directive"), + ); let _ = tracing_subscriber::fmt() .with_writer(logger) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 8e6051d533e..1f261d4155d 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -16,7 +16,7 @@ use matrix_sdk_crypto::{ }, decrypt_room_key_export, encrypt_room_key_export, olm::ExportedRoomKey, - store::RecoveryKey, + store::{Changes, RecoveryKey}, LocalTrust, OlmMachine as InnerMachine, UserIdentities, }; use ruma::{ @@ -54,8 +54,8 @@ use crate::{ BackupKeys, BackupRecoveryKey, BootstrapCrossSigningResult, CrossSigningKeyExport, CrossSigningStatus, DecodeError, DecryptedEvent, Device, DeviceLists, EncryptionSettings, EventEncryptionAlgorithm, KeyImportError, KeysImportResult, MegolmV1BackupKey, - ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, Sas, - SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, + ProgressListener, Request, RequestType, RequestVerificationResult, RoomKeyCounts, RoomSettings, + Sas, SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest, }; /// A high level state machine that handles E2EE for Matrix. @@ -563,21 +563,28 @@ impl OlmMachine { .map(|r| r.into())) } - /// TODO: docs - pub fn get_encryption_settings( + /// Get the stored room settings, such as the encryption algorithm or + /// whether to encrypt only for trusted devices. + /// + /// These settings can be modified via + /// [set_room_algorithm()](#method.set_room_algorithm) and + /// [set_room_only_allow_trusted_devices()](#method. + /// set_room_only_allow_trusted_devices) methods. + pub fn get_room_settings( &self, room_id: &str, - ) -> Result, CryptoStoreError> { + ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; let settings = self .runtime - .block_on(self.inner.store().get_encryption_settings(&room_id))? - .map(|v| v.into()); + .block_on(self.inner.store().get_room_settings(&room_id))? + .map(|v| v.try_into().unwrap()); Ok(settings) } - /// TODO: docs - pub fn set_event_encryption_algorithm( + /// Set the room algorithm used for encrypting messages to one of the + /// available variants + pub fn set_room_algorithm( &self, room_id: &str, algorithm: EventEncryptionAlgorithm, @@ -585,47 +592,71 @@ impl OlmMachine { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); + self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default(); settings.algorithm = algorithm.into(); - self.inner.store().set_encryption_settings(&room_id, settings).await?; + self.inner + .store() + .save_changes(Changes { + room_settings: HashMap::from([(room_id, settings)]), + ..Default::default() + }) + .await?; Ok(()) }) } - /// TODO: docs - pub fn set_block_untrusted_devices_per_room( + /// Set flag whether this room should encrypt messages for untrusted + /// devices, or whether they should be excluded from the conversation. + /// + /// Note that per-room setting may be overridden by a global + /// [set_only_allow_trusted_devices()](#method. + /// set_only_allow_trusted_devices) method. + pub fn set_room_only_allow_trusted_devices( &self, room_id: &str, - block_untrusted_devices: bool, + only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; self.runtime.block_on(async move { let mut settings = - self.inner.store().get_encryption_settings(&room_id).await?.unwrap_or_default(); - settings.only_allow_trusted_devices = block_untrusted_devices; - self.inner.store().set_encryption_settings(&room_id, settings).await?; + self.inner.store().get_room_settings(&room_id).await?.unwrap_or_default(); + settings.only_allow_trusted_devices = only_allow_trusted_devices; + self.inner + .store() + .save_changes(Changes { + room_settings: HashMap::from([(room_id, settings)]), + ..Default::default() + }) + .await?; Ok(()) }) } - /// TODO: docs - pub fn set_block_untrusted_devices_globally( + /// Check whether there is a global flag to only encrypt messages for + /// trusted devices or for everyone. + /// + /// Note that if the global flag is false, individual rooms may still be + /// encrypting only for trusted devices, depending on the per-room + /// `only_allow_trusted_devices` flag. + pub fn get_only_allow_trusted_devices(&self) -> Result { + let block = self.runtime.block_on(self.inner.store().get_only_allow_trusted_devices())?; + Ok(block) + } + + /// Set global flag whether to encrypt messages for untrusted devices, or + /// whether they should be excluded from the conversation. + /// + /// Note that if enabled, it will override any per-room settings. + pub fn set_only_allow_trusted_devices( &self, - block_untrusted_devices: bool, + only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { self.runtime.block_on( - self.inner.store().set_block_untrusted_devices_globally(block_untrusted_devices), + self.inner.store().set_only_allow_trusted_devices(only_allow_trusted_devices), )?; Ok(()) } - /// TODO: docs - pub fn is_blocking_untrusted_devices_globally(&self) -> Result { - let block = - self.runtime.block_on(self.inner.store().get_block_untrusted_devices_globally())?; - Ok(block) - } - /// Share a room key with the given list of users for the given room. /// /// After the request was sent out and a successful response was received @@ -643,40 +674,23 @@ impl OlmMachine { /// /// * `users` - The list of users which are considered to be members of the /// room and should receive the room key. + /// + /// * `settings` - The settings that should be used for the room key. pub fn share_room_key( &self, room_id: String, users: Vec, + settings: EncryptionSettings, ) -> Result, CryptoStoreError> { let users: Vec = users.into_iter().filter_map(|u| UserId::parse(u).ok()).collect(); let room_id = RoomId::parse(room_id)?; - let requests = self.runtime.block_on(async move { - // Load settings from store or create new - let mut encryption_settings = if let Some(settings) = - self.inner.store().get_encryption_settings(room_id.into()).await? - { - settings - } else { - let settings = EncryptionSettings::default(); - self.inner.store().set_encryption_settings(room_id.into(), settings).await?; - settings - }; - - // Combine per-room and global unverified devices block - if !encryption_settings.only_allow_trusted_devices { - let block_globally = self - .inner - .store() - .get_block_untrusted_devices_globally() - .await? - .unwrap_or_default(); - encryption_settings.only_allow_trusted_devices = block_globally - } - - self.inner.share_room_key(&room_id, users.iter().map(Deref::deref), settings).await - }); + let requests = self.runtime.block_on(self.inner.share_room_key( + &room_id, + users.iter().map(Deref::deref), + settings, + ))?; Ok(requests.into_iter().map(|r| r.as_ref().into()).collect()) } diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index cd1dbd1745c..e43ab2a841d 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -296,7 +296,7 @@ impl OlmMachine { Ok(OlmMachine::new_helper(user_id, device_id, store, account, identity)) } - /// TODO: docs + /// Get the crypto store associated with this `OlmMachine` instance. pub fn store(&self) -> &Store { &self.store } @@ -756,7 +756,8 @@ impl OlmMachine { /// /// `users` - The list of users that should receive the room key. /// - /// `settings` - TODO: + /// `settings` - Encryption settings that affect when are room keys rotated + /// and who are they shared with pub async fn share_room_key( &self, room_id: &RoomId, diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index b80414ff204..bc700b08fab 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -17,10 +17,12 @@ macro_rules! cryptostore_integration_tests { }, store::{ Changes, CryptoStore, DeviceChanges, GossipRequest, IdentityChanges, - RecoveryKey, + RecoveryKey, RoomSettings, }, testing::{get_device, get_other_identity, get_own_identity}, - types::events::room_key_request::MegolmV1AesSha2Content, + types::{ + events::room_key_request::MegolmV1AesSha2Content, EventEncryptionAlgorithm, + }, ReadOnlyDevice, SecretInfo, TrackedUser, }; @@ -646,6 +648,56 @@ macro_rules! cryptostore_integration_tests { "The loaded version matches to the one we stored" ); } + + #[async_test] + async fn room_settings_saving() { + let (account, store) = get_loaded_store("room_settings_saving").await; + + let room_1 = room_id!("!test_1:localhost"); + let settings_1 = RoomSettings { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: true, + }; + + let room_2 = room_id!("!test_2:localhost"); + let settings_2 = RoomSettings { + algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, + only_allow_trusted_devices: false, + }; + + let room_3 = room_id!("!test_3:localhost"); + + let changes = Changes { + room_settings: HashMap::from([ + (room_1.into(), settings_1.clone()), + (room_2.into(), settings_2.clone()), + ]), + ..Default::default() + }; + + store.save_changes(changes).await.unwrap(); + + let loaded_settings_1 = store.get_room_settings(room_1).await.unwrap(); + assert_eq!(Some(settings_1), loaded_settings_1); + + let loaded_settings_2 = store.get_room_settings(room_2).await.unwrap(); + assert_eq!(Some(settings_2), loaded_settings_2); + + let loaded_settings_3 = store.get_room_settings(room_3).await.unwrap(); + assert_eq!(None, loaded_settings_3); + } + + #[async_test] + async fn custom_value_saving() { + let (account, store) = get_loaded_store("custom_value_saving").await; + store.set_custom_value("A", "Hello".as_bytes().to_vec()).await.unwrap(); + + let loaded_1 = store.get_custom_value("A").await.unwrap(); + assert_eq!(Some("Hello".as_bytes().to_vec()), loaded_1); + + let loaded_2 = store.get_custom_value("B").await.unwrap(); + assert_eq!(None, loaded_2); + } } }; } diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 6ef56f9a42e..7d31264358e 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -20,10 +20,12 @@ use matrix_sdk_common::locks::Mutex; use ruma::{ DeviceId, OwnedDeviceId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId, }; +use tracing::warn; use super::{ caches::{DeviceStore, GroupSessionStore, SessionStore}, - BackupKeys, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, RoomKeyCounts, Session, + BackupKeys, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, RoomKeyCounts, + RoomSettings, Session, }; use crate::{ gossiping::{GossipRequest, SecretInfo}, @@ -271,11 +273,18 @@ impl CryptoStore for MemoryStore { Ok(BackupKeys::default()) } + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); Ok(None) } async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); Ok(()) } } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 2585ad46ef7..898234e397a 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -49,7 +49,7 @@ use atomic::Ordering; use dashmap::DashSet; use matrix_sdk_common::locks::Mutex; use ruma::{ - events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedUserId, RoomId, UserId, + events::secret::request::SecretName, DeviceId, OwnedDeviceId, OwnedRoomId, OwnedUserId, UserId, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; @@ -66,9 +66,10 @@ use crate::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, ReadOnlyAccount, Session, }, + types::EventEncryptionAlgorithm, utilities::encode, verification::VerificationMachine, - CrossSigningStatus, EncryptionSettings, + CrossSigningStatus, }; pub mod caches; @@ -120,6 +121,7 @@ pub struct Changes { pub key_requests: Vec, pub identities: IdentityChanges, pub devices: DeviceChanges, + pub room_settings: HashMap, } /// A user for which we are tracking the list of devices. @@ -279,6 +281,25 @@ pub enum SecretImportError { Store(#[from] CryptoStoreError), } +/// Room encryption settings which are modified by state events or user options +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct RoomSettings { + /// The encryption algorithm that should be used in the room. + pub algorithm: EventEncryptionAlgorithm, + /// Should untrusted devices receive the room key, or should they be + /// excluded from the conversation. + pub only_allow_trusted_devices: bool, +} + +impl Default for RoomSettings { + fn default() -> Self { + Self { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: false, + } + } +} + impl Store { /// Create a new Store pub fn new( @@ -754,40 +775,23 @@ impl Store { Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) } - /// TODO: docs - pub async fn get_encryption_settings( - &self, - room_id: &RoomId, - ) -> Result> { - let key = format!("encryption_settings-{room_id}"); - self.get_value(&key).await - } - - /// TODO: docs - pub async fn set_encryption_settings( - &self, - room_id: &RoomId, - settings: EncryptionSettings, - ) -> Result<()> { - let key = format!("encryption_settings-{room_id}"); - self.set_value(&key, &settings).await - } - - /// TODO: docs - pub async fn get_block_untrusted_devices_globally(&self) -> Result { - let value = self.get_value("block_untrusted_devices").await?.unwrap_or_default(); + /// Check whether there is a global flag to only encrypt messages for + /// trusted devices or for everyone. + pub async fn get_only_allow_trusted_devices(&self) -> Result { + let value = self.get_value("only_allow_trusted_devices").await?.unwrap_or_default(); Ok(value) } - /// TODO: docs - pub async fn set_block_untrusted_devices_globally( + /// Set global flag whether to encrypt messages for untrusted devices, or + /// whether they should be excluded from the conversation. + pub async fn set_only_allow_trusted_devices( &self, block_untrusted_devices: bool, ) -> Result<()> { - self.set_value("block_untrusted_devices", &block_untrusted_devices).await + self.set_value("only_allow_trusted_devices", &block_untrusted_devices).await } - /// TODO: docs + /// Get custom stored value associated with a key pub async fn get_value(&self, key: &str) -> Result> { let Some(value) = self.get_custom_value(key).await? else { return Ok(None); @@ -796,7 +800,7 @@ impl Store { Ok(Some(deserialized)) } - /// TODO: docs + /// Store custom value associated with a key pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> { let serialized = self.serialize_value(value)?; self.set_custom_value(key, serialized).await?; @@ -805,7 +809,7 @@ impl Store { fn serialize_value(&self, value: &impl Serialize) -> Result> { let serialized = - rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; // TODO: Serialization type + rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?; Ok(serialized) } diff --git a/crates/matrix-sdk-crypto/src/store/traits.rs b/crates/matrix-sdk-crypto/src/store/traits.rs index e58ea79e2c6..43d7021b2da 100644 --- a/crates/matrix-sdk-crypto/src/store/traits.rs +++ b/crates/matrix-sdk-crypto/src/store/traits.rs @@ -18,7 +18,7 @@ use async_trait::async_trait; use matrix_sdk_common::{locks::Mutex, AsyncTraitDeps}; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; -use super::{BackupKeys, Changes, CryptoStoreError, Result, RoomKeyCounts}; +use super::{BackupKeys, Changes, CryptoStoreError, Result, RoomKeyCounts, RoomSettings}; use crate::{ olm::{ InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, @@ -187,6 +187,17 @@ pub trait CryptoStore: AsyncTraitDeps { request_id: &TransactionId, ) -> Result<(), Self::Error>; + /// Get the room settings, such as the encryption algorithm or whether to + /// encrypt only for trusted devices. + /// + /// # Arguments + /// + /// * `room_id` - The room id of the room + async fn get_room_settings( + &self, + room_id: &RoomId, + ) -> Result, Self::Error>; + /// Get arbitrary data from the store /// /// # Arguments @@ -329,6 +340,10 @@ impl CryptoStore for EraseCryptoStoreError { self.0.delete_outgoing_secret_requests(request_id).await.map_err(Into::into) } + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + self.0.get_room_settings(room_id).await.map_err(Into::into) + } + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error> { self.0.get_custom_value(key).await.map_err(Into::into) } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index cbb60e18e44..d862e328edd 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -28,6 +28,7 @@ use matrix_sdk_crypto::{ }, store::{ caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, RoomKeyCounts, + RoomSettings, }, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, TrackedUser, @@ -35,6 +36,7 @@ use matrix_sdk_crypto::{ use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; use serde::{de::DeserializeOwned, Serialize}; +use tracing::warn; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -950,6 +952,21 @@ impl_crypto_store! { Ok(key) } + + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); + Ok(()) + } } impl Drop for IndexeddbCryptoStore { diff --git a/crates/matrix-sdk-sled/src/crypto_store.rs b/crates/matrix-sdk-sled/src/crypto_store.rs index 4b654824eb1..ef8c5a9931f 100644 --- a/crates/matrix-sdk-sled/src/crypto_store.rs +++ b/crates/matrix-sdk-sled/src/crypto_store.rs @@ -28,7 +28,7 @@ use matrix_sdk_crypto::{ }, store::{ caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, Result, - RoomKeyCounts, + RoomKeyCounts, RoomSettings, }, types::{events::room_key_request::SupportedKeyInfo, EventEncryptionAlgorithm}, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, @@ -41,7 +41,7 @@ use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Batch, Config, Db, IVec, Transactional, Tree, }; -use tracing::debug; +use tracing::{debug, warn}; use super::OpenStoreError; use crate::encode_key::{EncodeKey, ENCODE_SEPARATOR}; @@ -1010,6 +1010,21 @@ impl CryptoStore for SledCryptoStore { Ok(key) } + + async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { + warn!("Method not implemented"); + Ok(None) + } + + async fn get_custom_value(&self, _key: &str) -> Result>> { + warn!("Method not implemented"); + Ok(None) + } + + async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { + warn!("Method not implemented"); + Ok(()) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql b/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql new file mode 100644 index 00000000000..91fcbae1fd4 --- /dev/null +++ b/crates/matrix-sdk-sqlite/migrations/003_room_settings.sql @@ -0,0 +1,4 @@ +CREATE TABLE room_settings( + "room_id" BLOB PRIMARY KEY NOT NULL, + "data" BLOB NOT NULL +); diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 1c90b44b4b1..eedf4f0132b 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -27,7 +27,7 @@ use matrix_sdk_crypto::{ IdentityKeys, InboundGroupSession, OutboundGroupSession, PickledInboundGroupSession, PrivateCrossSigningIdentity, Session, }, - store::{caches::SessionStore, BackupKeys, Changes, CryptoStore, RoomKeyCounts}, + store::{caches::SessionStore, BackupKeys, Changes, CryptoStore, RoomKeyCounts, RoomSettings}, GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, TrackedUser, }; @@ -172,7 +172,7 @@ impl SqliteCryptoStore { } } -const DATABASE_VERSION: u8 = 2; +const DATABASE_VERSION: u8 = 3; async fn run_migrations(conn: &SqliteConn) -> rusqlite::Result<()> { let kv_exists = conn @@ -221,6 +221,13 @@ async fn run_migrations(conn: &SqliteConn) -> rusqlite::Result<()> { .await?; } + if version < 3 { + conn.with_transaction(|txn| { + txn.execute_batch(include_str!("../migrations/003_room_settings.sql")) + }) + .await?; + } + conn.set_kv("version", vec![DATABASE_VERSION]).await?; Ok(()) @@ -257,6 +264,8 @@ trait SqliteConnectionExt { sent_out: bool, data: &[u8], ) -> rusqlite::Result<()>; + + fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()>; } impl SqliteConnectionExt for rusqlite::Connection { @@ -348,6 +357,16 @@ impl SqliteConnectionExt for rusqlite::Connection { )?; Ok(()) } + + fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()> { + self.execute( + "INSERT INTO room_settings (room_id, data) + VALUES (?1, ?2) + ON CONFLICT (room_id) DO UPDATE SET data = ?2", + (room_id, data), + )?; + Ok(()) + } } #[async_trait] @@ -515,6 +534,15 @@ trait SqliteObjectCryptoStoreExt: SqliteObjectExt { self.execute("DELETE FROM key_requests WHERE request_id = ?", (request_id,)).await?; Ok(()) } + + async fn get_room_settings(&self, room_id: Key) -> Result>> { + Ok(self + .query_row("SELECT data FROM room_settings WHERE room_id = ?", (room_id,), |row| { + row.get(0) + }) + .await + .optional()?) + } } #[async_trait] @@ -690,6 +718,12 @@ impl CryptoStore for SqliteCryptoStore { txn.set_key_request(&request_id, request.sent_out, &serialized_request)?; } + for (room_id, settings) in changes.room_settings { + let room_id = this.encode_key("room_settings", room_id.as_bytes()); + let value = this.serialize_value(&settings)?; + txn.set_room_settings(&room_id, &value)?; + } + Ok::<_, Error>(()) }) .await?; @@ -947,6 +981,17 @@ impl CryptoStore for SqliteCryptoStore { Ok(self.acquire().await?.delete_key_request(request_id).await?) } + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let room_id = self.encode_key("room_settings", room_id.as_bytes()); + let Some(value) = self.acquire().await?.get_room_settings(room_id).await? else { + return Ok(None); + }; + + let settings = self.deserialize_value(&value)?; + + return Ok(Some(settings)); + } + async fn get_custom_value(&self, key: &str) -> Result>> { let Some(serialized) = self.acquire().await?.get_kv(key).await? else { return Ok(None); From 51b3bf4c75b05369d744178376c813597fb76e32 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 2 Mar 2023 10:16:01 +0000 Subject: [PATCH 21/28] Add room settings migration --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 76 +++++++++++++++++++++- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 1 + 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index bc8759ef3a8..62481a29a0b 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -76,6 +76,8 @@ pub struct MigrationData { cross_signing: CrossSigningKeyExport, /// The list of users that the Rust SDK should track. tracked_users: Vec, + /// Map of room settings + room_settings: HashMap, } /// Struct collecting data that is important to migrate sessions to the rust-sdk @@ -201,6 +203,48 @@ pub fn migrate( }) } +/// Migrate room settings, including room algorithm and whether to block +/// untrusted devices from legacy store to Sqlite store. +/// +/// Note that this method should only be used if a client has already migrated +/// account data via [migrate](#method.migrate) method, which did not include +/// room settings. For a brand new migration, the [migrate](#method.migrate) +/// method will take care of room settings automatically, if provided. +/// +/// # Arguments +/// +/// * `room_settings` - Map of room settings +/// +/// * `path` - The path where the Sqlite store should be created. +/// +/// * `passphrase` - The passphrase that should be used to encrypt the data at +/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store +/// and all its data will remain unencrypted. +pub fn migrate_room_settings( + room_settings: HashMap, + path: &str, + passphrase: Option, +) -> anyhow::Result<()> { + use matrix_sdk_crypto::store::{Changes as RustChanges, CryptoStore}; + use tokio::runtime::Runtime; + + let runtime = Runtime::new()?; + runtime.block_on(async move { + let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; + + let mut rust_settings = HashMap::new(); + for (room_id, settings) in room_settings { + let room_id = RoomId::parse(room_id)?; + rust_settings.insert(room_id, settings.into()); + } + + let changes = RustChanges { room_settings: rust_settings, ..Default::default() }; + store.save_changes(changes).await?; + + Ok(()) + }) +} + async fn migrate_data( mut data: MigrationData, path: &str, @@ -297,6 +341,12 @@ async fn migrate_data( processed_steps += 1; listener(processed_steps, total_steps); + let mut room_settings = HashMap::new(); + for (room_id, settings) in data.room_settings { + let room_id = RoomId::parse(room_id)?; + room_settings.insert(room_id, settings.into()); + } + let changes = Changes { account: Some(account), private_identity: Some(cross_signing), @@ -304,6 +354,7 @@ async fn migrate_data( inbound_group_sessions, recovery_key, backup_version: data.backup_version, + room_settings, ..Default::default() }; @@ -491,6 +542,7 @@ impl ProgressListener for T { } /// An encryption algorithm to be used to encrypt messages sent to a room. +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub enum EventEncryptionAlgorithm { /// Olm version 1 using Curve25519, AES-256, and SHA-256. OlmV1Curve25519AesSha2, @@ -721,6 +773,7 @@ impl From for CrossSigningStatus { } /// Room encryption settings which are modified by state events or user options +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct RoomSettings { /// The encryption algorithm that should be used in the room. pub algorithm: EventEncryptionAlgorithm, @@ -774,7 +827,7 @@ mod test { use tempfile::tempdir; use super::MigrationData; - use crate::{migrate, OlmMachine}; + use crate::{migrate, OlmMachine, RoomSettings, EventEncryptionAlgorithm}; #[test] fn android_migration() -> Result<()> { @@ -857,7 +910,17 @@ mod test { "@this-is-me:matrix.org", "@Amandine:matrix.org", "@ganfra:matrix.org" - ] + ], + "room_settings": { + "!AZkqtjvtwPAuyNOXEt:matrix.org": { + "algorithm": "OlmV1Curve25519AesSha2", + "only_allow_trusted_devices": true + }, + "!CWLUCoEWXSFyTCOtfL:matrix.org": { + "algorithm": "MegolmV1AesSha2", + "only_allow_trusted_devices": false + }, + } }); let migration_data: MigrationData = serde_json::from_value(data)?; @@ -886,6 +949,15 @@ mod test { let backup_keys = machine.get_backup_keys()?; assert!(backup_keys.is_some()); + let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; + assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, only_allow_trusted_devices: true }), settings1); + + let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; + assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, only_allow_trusted_devices: false }), settings2); + + let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; + assert!(settings3.is_none()); + Ok(()) } } diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 62aa0ffbc30..679e504e3cf 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -429,6 +429,7 @@ dictionary MigrationData { sequence pickle_key; CrossSigningKeyExport cross_signing; sequence tracked_users; + record room_settings; }; dictionary SessionMigrationData { From 8f1302333311cd69ad1d19cd5770fbe96f97cb71 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 10:16:30 +0000 Subject: [PATCH 22/28] Missing stores --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 20 +++++-- .../matrix-sdk-indexeddb/src/crypto_store.rs | 60 ++++++++++++++++--- crates/matrix-sdk-sled/src/crypto_store.rs | 42 +++++++++---- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 62481a29a0b..c44ec6cf273 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -827,7 +827,7 @@ mod test { use tempfile::tempdir; use super::MigrationData; - use crate::{migrate, OlmMachine, RoomSettings, EventEncryptionAlgorithm}; + use crate::{migrate, EventEncryptionAlgorithm, OlmMachine, RoomSettings}; #[test] fn android_migration() -> Result<()> { @@ -950,13 +950,25 @@ mod test { assert!(backup_keys.is_some()); let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; - assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, only_allow_trusted_devices: true }), settings1); + assert_eq!( + Some(RoomSettings { + algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, + only_allow_trusted_devices: true + }), + settings1 + ); let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; - assert_eq!(Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, only_allow_trusted_devices: false }), settings2); + assert_eq!( + Some(RoomSettings { + algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, + only_allow_trusted_devices: false + }), + settings2 + ); let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; - assert!(settings3.is_none()); + assert!(settings3.is_none()); Ok(()) } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index d862e328edd..3403d1c01c3 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -36,7 +36,6 @@ use matrix_sdk_crypto::{ use matrix_sdk_store_encryption::StoreCipher; use ruma::{DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId}; use serde::{de::DeserializeOwned, Serialize}; -use tracing::warn; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -61,6 +60,7 @@ mod keys { pub const UNSENT_SECRET_REQUESTS: &str = "unsent_secret_requests"; pub const SECRET_REQUESTS_BY_INFO: &str = "secret_requests_by_info"; pub const KEY_REQUEST: &str = "key_request"; + pub const ROOM_SETTINGS: &str = "room_settings"; // keys pub const STORE_CIPHER: &str = "store_cipher"; @@ -167,6 +167,9 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::SECRET_REQUESTS_BY_INFO)?; db.create_object_store(keys::BACKUP_KEYS)?; + + db.create_object_store(keys::ROOM_SETTINGS)?; + } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -365,9 +368,20 @@ impl_crypto_store! { !changes.identities.new.is_empty() || !changes.identities.changed.is_empty(), keys::IDENTITIES, ), +<<<<<<< HEAD (!changes.inbound_group_sessions.is_empty(), keys::INBOUND_GROUP_SESSIONS), (!changes.outbound_group_sessions.is_empty(), keys::OUTBOUND_GROUP_SESSIONS), (!changes.message_hashes.is_empty(), keys::OLM_HASHES), +||||||| parent of b0932a23c (Missing stores) + (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), + (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), + (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), +======= + (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), + (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), + (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), + (!changes.room_settings.is_empty(), KEYS::ROOM_SETTINGS), +>>>>>>> b0932a23c (Missing stores) ] .iter() .filter_map(|(id, key)| if *id { Some(*key) } else { None }) @@ -476,6 +490,7 @@ impl_crypto_store! { let identity_changes = changes.identities; let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; + let room_settings_changes = changes.room_settings; if !device_changes.new.is_empty() || !device_changes.changed.is_empty() { let device_store = tx.object_store(keys::DEVICES)?; @@ -540,6 +555,16 @@ impl_crypto_store! { } } + if !room_settings_changes.is_empty() { + let settings_store = tx.object_store(KEYS::ROOM_SETTINGS)?; + + for (room_id, settings) in &room_settings_changes { + let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let value = self.serialize_value(&settings)?; + settings_store.put_key_val(&key, &value)?; + } + } + tx.await.into_result()?; // all good, let's update our caches:indexeddb @@ -953,18 +978,35 @@ impl_crypto_store! { Ok(key) } - async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { - warn!("Method not implemented"); - Ok(None) + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + Ok(self + .inner + .transaction_on_one_with_mode(KEYS::ROOM_SETTINGS, IdbTransactionMode::Readonly)? + .object_store(KEYS::ROOM_SETTINGS)? + .get(&key)? + .await? + .map(|v| self.deserialize_value(v)) + .transpose()?) } - async fn get_custom_value(&self, _key: &str) -> Result>> { - warn!("Method not implemented"); - Ok(None) + async fn get_custom_value(&self, key: &str) -> Result>> { + Ok(self + .inner + .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)? + .object_store(KEYS::CORE)? + .get(&JsValue::from_str(key))? + .await? + .map(|v| self.deserialize_value(v)) + .transpose()?) } - async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { - warn!("Method not implemented"); + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + self + .inner + .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readwrite)? + .object_store(KEYS::CORE)? + .put_key_val(&JsValue::from_str(key), &self.serialize_value(&value)?)?; Ok(()) } } diff --git a/crates/matrix-sdk-sled/src/crypto_store.rs b/crates/matrix-sdk-sled/src/crypto_store.rs index ef8c5a9931f..01652fb597b 100644 --- a/crates/matrix-sdk-sled/src/crypto_store.rs +++ b/crates/matrix-sdk-sled/src/crypto_store.rs @@ -41,12 +41,12 @@ use sled::{ transaction::{ConflictableTransactionError, TransactionError}, Batch, Config, Db, IVec, Transactional, Tree, }; -use tracing::{debug, warn}; +use tracing::debug; use super::OpenStoreError; use crate::encode_key::{EncodeKey, ENCODE_SEPARATOR}; -const DATABASE_VERSION: u8 = 6; +const DATABASE_VERSION: u8 = 7; // Table names that are used to derive a separate key for each tree. This ensure // that user ids encoded for different trees won't end up as the same byte @@ -58,6 +58,7 @@ const INBOUND_GROUP_TABLE_NAME: &str = "crypto-store-inbound-group-sessions"; const OUTBOUND_GROUP_TABLE_NAME: &str = "crypto-store-outbound-group-sessions"; const SECRET_REQUEST_BY_INFO_TABLE: &str = "crypto-store-secret-request-by-info"; const TRACKED_USERS_TABLE: &str = "crypto-store-secret-tracked-users"; +const ROOM_SETTINGS_TABLE: &str = "crypto-store-secret-room-settings"; impl EncodeKey for InboundGroupSession { fn encode(&self) -> Vec { @@ -185,6 +186,8 @@ pub struct SledCryptoStore { identities: Tree, tracked_users: Tree, + + room_settings: Tree, } impl std::fmt::Debug for SledCryptoStore { @@ -386,6 +389,8 @@ impl SledCryptoStore { let unsent_secret_requests = db.open_tree("unsent_secret_requests")?; let secret_requests_by_info = db.open_tree("secret_requests_by_info")?; + let room_settings = db.open_tree("room_settings")?; + let session_cache = SessionStore::new(); let database = Self { @@ -406,6 +411,7 @@ impl SledCryptoStore { tracked_users, olm_hashes, identities, + room_settings, }; database.upgrade().await?; @@ -499,6 +505,7 @@ impl SledCryptoStore { let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; let backup_version = changes.backup_version; + let room_settings_changes = changes.room_settings; let ret: Result<(), TransactionError> = ( &self.account, @@ -512,6 +519,7 @@ impl SledCryptoStore { &self.outgoing_secret_requests, &self.unsent_secret_requests, &self.secret_requests_by_info, + &self.room_settings, ) .transaction( |( @@ -526,6 +534,7 @@ impl SledCryptoStore { outgoing_secret_requests, unsent_secret_requests, secret_requests_by_info, + room_settings, )| { if let Some(a) = &account_pickle { account.insert( @@ -635,6 +644,15 @@ impl SledCryptoStore { } } + for (room_id, settings) in &room_settings_changes { + let key = self.encode_key(ROOM_SETTINGS_TABLE, room_id); + room_settings.insert( + key.as_slice(), + self.serialize_value(&settings) + .map_err(ConflictableTransactionError::Abort)?, + )?; + } + Ok(()) }, ); @@ -1011,18 +1029,22 @@ impl CryptoStore for SledCryptoStore { Ok(key) } - async fn get_room_settings(&self, _room_id: &RoomId) -> Result> { - warn!("Method not implemented"); - Ok(None) + async fn get_room_settings(&self, room_id: &RoomId) -> Result> { + let key = self.encode_key(ROOM_SETTINGS_TABLE, room_id); + self.room_settings + .get(key) + .map_err(CryptoStoreError::backend)? + .map(|p| self.deserialize_value(&p)) + .transpose() } - async fn get_custom_value(&self, _key: &str) -> Result>> { - warn!("Method not implemented"); - Ok(None) + async fn get_custom_value(&self, key: &str) -> Result>> { + let value = self.inner.get(key).map_err(CryptoStoreError::backend)?.map(|v| v.to_vec()); + Ok(value) } - async fn set_custom_value(&self, _key: &str, _value: Vec) -> Result<()> { - warn!("Method not implemented"); + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { + self.inner.insert(key, value).map_err(CryptoStoreError::backend)?; Ok(()) } } From 98ca774118d82112db8de3e0b05be15539767bf8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 15:53:53 +0000 Subject: [PATCH 23/28] Constrain store methods --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 11 ++-- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 3 +- crates/matrix-sdk-crypto/src/store/mod.rs | 52 +++++++++---------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index c44ec6cf273..06ca4540414 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -225,9 +225,6 @@ pub fn migrate_room_settings( path: &str, passphrase: Option, ) -> anyhow::Result<()> { - use matrix_sdk_crypto::store::{Changes as RustChanges, CryptoStore}; - use tokio::runtime::Runtime; - let runtime = Runtime::new()?; runtime.block_on(async move { let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; @@ -238,7 +235,7 @@ pub fn migrate_room_settings( rust_settings.insert(room_id, settings.into()); } - let changes = RustChanges { room_settings: rust_settings, ..Default::default() }; + let changes = Changes { room_settings: rust_settings, ..Default::default() }; store.save_changes(changes).await?; Ok(()) @@ -560,7 +557,7 @@ impl From for RustEventEncryptionAlgorithm { } impl TryFrom for EventEncryptionAlgorithm { - type Error = String; + type Error = serde_json::Error; fn try_from(value: RustEventEncryptionAlgorithm) -> Result { match value { @@ -568,7 +565,7 @@ impl TryFrom for EventEncryptionAlgorithm { Ok(Self::OlmV1Curve25519AesSha2) } RustEventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(Self::MegolmV1AesSha2), - _ => Err(format!("Unsupported algorithm {value}")), + _ => Err(serde::de::Error::custom(format!("Unsupported algorithm {value}"))), } } } @@ -783,7 +780,7 @@ pub struct RoomSettings { } impl TryFrom for RoomSettings { - type Error = String; + type Error = serde_json::Error; fn try_from(value: RustRoomSettings) -> Result { let algorithm = value.algorithm.try_into()?; diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 1f261d4155d..fbade20e9d2 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -578,7 +578,8 @@ impl OlmMachine { let settings = self .runtime .block_on(self.inner.store().get_room_settings(&room_id))? - .map(|v| v.try_into().unwrap()); + .map(|v| v.try_into()) + .transpose()?; Ok(settings) } diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 898234e397a..6564d6bc6bc 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -302,7 +302,7 @@ impl Default for RoomSettings { impl Store { /// Create a new Store - pub fn new( + pub(crate) fn new( user_id: Arc, identity: Arc>, store: Arc, @@ -321,33 +321,33 @@ impl Store { } /// UserId associated with this store - pub fn user_id(&self) -> &UserId { + pub(crate) fn user_id(&self) -> &UserId { &self.user_id } /// DeviceId associated with this store - pub fn device_id(&self) -> &DeviceId { + pub(crate) fn device_id(&self) -> &DeviceId { self.verification_machine.own_device_id() } /// The Account associated with this store - pub fn account(&self) -> &ReadOnlyAccount { + pub(crate) fn account(&self) -> &ReadOnlyAccount { &self.verification_machine.store.account } #[cfg(test)] /// test helper to reset the cross signing identity - pub async fn reset_cross_signing_identity(&self) { + pub(crate) async fn reset_cross_signing_identity(&self) { self.identity.lock().await.reset().await; } /// PrivateCrossSigningIdentity associated with this store - pub fn private_identity(&self) -> Arc> { + pub(crate) fn private_identity(&self) -> Arc> { self.identity.clone() } /// Save the given Sessions to the store - pub async fn save_sessions(&self, sessions: &[Session]) -> Result<()> { + pub(crate) async fn save_sessions(&self, sessions: &[Session]) -> Result<()> { let changes = Changes { sessions: sessions.to_vec(), ..Default::default() }; self.save_changes(changes).await @@ -359,7 +359,7 @@ impl Store { /// This method returns `SessionOrdering::Better` if the given session is /// better than the one we already have or if we don't have such a /// session in the store. - pub async fn compare_group_session( + pub(crate) async fn compare_group_session( &self, session: &InboundGroupSession, ) -> Result { @@ -375,7 +375,7 @@ impl Store { #[cfg(test)] /// Testing helper to allow to save only a set of devices - pub async fn save_devices(&self, devices: &[ReadOnlyDevice]) -> Result<()> { + pub(crate) async fn save_devices(&self, devices: &[ReadOnlyDevice]) -> Result<()> { let changes = Changes { devices: DeviceChanges { changed: devices.to_vec(), ..Default::default() }, ..Default::default() @@ -386,7 +386,7 @@ impl Store { #[cfg(test)] /// Testing helper to allo to save only a set of InboundGroupSession - pub async fn save_inbound_group_sessions( + pub(crate) async fn save_inbound_group_sessions( &self, sessions: &[InboundGroupSession], ) -> Result<()> { @@ -396,7 +396,7 @@ impl Store { } /// Get the display name of our own device. - pub async fn device_display_name(&self) -> Result, CryptoStoreError> { + pub(crate) async fn device_display_name(&self) -> Result, CryptoStoreError> { Ok(self .inner .get_device(self.user_id(), self.device_id()) @@ -405,7 +405,7 @@ impl Store { } /// Get the read-only device associated with `device_id` for `user_id` - pub async fn get_readonly_device( + pub(crate) async fn get_readonly_device( &self, user_id: &UserId, device_id: &DeviceId, @@ -416,7 +416,7 @@ impl Store { /// Get the read-only version of all the devices that the given user has. /// /// *Note*: This doesn't return our own device. - pub async fn get_readonly_devices_filtered( + pub(crate) async fn get_readonly_devices_filtered( &self, user_id: &UserId, ) -> Result> { @@ -431,7 +431,7 @@ impl Store { /// Get the read-only version of all the devices that the given user has. /// /// *Note*: This does also return our own device. - pub async fn get_readonly_devices_unfiltered( + pub(crate) async fn get_readonly_devices_unfiltered( &self, user_id: &UserId, ) -> Result> { @@ -441,7 +441,7 @@ impl Store { /// Get a device for the given user with the given curve25519 key. /// /// *Note*: This doesn't return our own device. - pub async fn get_device_from_curve_key( + pub(crate) async fn get_device_from_curve_key( &self, user_id: &UserId, curve_key: Curve25519PublicKey, @@ -454,7 +454,7 @@ impl Store { /// Get all devices associated with the given `user_id` /// /// *Note*: This doesn't return our own device. - pub async fn get_user_devices_filtered(&self, user_id: &UserId) -> Result { + pub(crate) async fn get_user_devices_filtered(&self, user_id: &UserId) -> Result { self.get_user_devices(user_id).await.map(|mut d| { if user_id == self.user_id() { d.inner.remove(self.device_id()); @@ -466,7 +466,7 @@ impl Store { /// Get all devices associated with the given `user_id` /// /// *Note*: This does also return our own device. - pub async fn get_user_devices(&self, user_id: &UserId) -> Result { + pub(crate) async fn get_user_devices(&self, user_id: &UserId) -> Result { let devices = self.get_readonly_devices_unfiltered(user_id).await?; let own_identity = @@ -482,7 +482,7 @@ impl Store { } /// Get a Device copy associated with `device_id` for `user_id` - pub async fn get_device( + pub(crate) async fn get_device( &self, user_id: &UserId, device_id: &DeviceId, @@ -500,7 +500,7 @@ impl Store { } /// Get the Identity of `user_id` - pub async fn get_identity(&self, user_id: &UserId) -> Result> { + pub(crate) async fn get_identity(&self, user_id: &UserId) -> Result> { // let own_identity = // self.inner.get_user_identity(self.user_id()).await?.and_then(|i| i.own()); Ok(if let Some(identity) = self.inner.get_user_identity(user_id).await? { @@ -540,7 +540,7 @@ impl Store { /// # Arguments /// /// * `secret_name` - The name of the secret that should be exported. - pub async fn export_secret(&self, secret_name: &SecretName) -> Option { + pub(crate) async fn export_secret(&self, secret_name: &SecretName) -> Option { match secret_name { SecretName::CrossSigningMasterKey | SecretName::CrossSigningUserSigningKey @@ -567,7 +567,7 @@ impl Store { } /// Import the Cross Signing Keys - pub async fn import_cross_signing_keys( + pub(crate) async fn import_cross_signing_keys( &self, export: CrossSigningKeyExport, ) -> Result { @@ -597,7 +597,7 @@ impl Store { } /// Import the given `secret` named `secret_name` into the keystore. - pub async fn import_secret( + pub(crate) async fn import_secret( &self, secret_name: &SecretName, secret: &str, @@ -642,7 +642,7 @@ impl Store { /// /// This means that the user will be considered for a `/keys/query` request /// next time [`Store::users_for_key_query()`] is called. - pub async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> { + pub(crate) async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> { self.users_for_key_query.lock().await.insert_user(user); self.tracked_users_cache.insert(user.to_owned()); @@ -653,7 +653,7 @@ impl Store { /// /// Any users not already on the list are flagged as awaiting a key query. /// Users that were already in the list are unaffected. - pub async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { + pub(crate) async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { self.load_tracked_users().await?; let mut store_updates = Vec::new(); @@ -762,14 +762,14 @@ impl Store { /// A pair `(users, sequence_number)`, where `users` is the list of users to /// be queried, and `sequence_number` is the current sequence number, /// which should be returned in `mark_tracked_users_as_up_to_date`. - pub async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { + pub(crate) async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { self.load_tracked_users().await?; Ok(self.users_for_key_query.lock().await.users_for_key_query()) } /// See the docs for [`crate::OlmMachine::tracked_users()`]. - pub async fn tracked_users(&self) -> Result> { + pub(crate) async fn tracked_users(&self) -> Result> { self.load_tracked_users().await?; Ok(self.tracked_users_cache.iter().map(|u| u.clone()).collect()) From 1177bdd3942c20e7752a932ca368fe5b6cbefb4c Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 7 Mar 2023 15:58:21 +0000 Subject: [PATCH 24/28] Indexdb migration --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 3 +-- .../matrix-sdk-indexeddb/src/crypto_store.rs | 22 +++++++------------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 06ca4540414..12cb12fe8c5 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -32,8 +32,7 @@ use matrix_sdk_common::deserialized_responses::VerificationState; use matrix_sdk_crypto::{ backups::SignatureState, olm::{IdentityKeys, InboundGroupSession, Session}, - store::{Changes, CryptoStore}, - store::RoomSettings as RustRoomSettings, + store::{Changes, CryptoStore, RoomSettings as RustRoomSettings}, types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey}, EncryptionSettings as RustEncryptionSettings, LocalTrust, }; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index 3403d1c01c3..1d5c6cc6fbd 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -144,7 +144,7 @@ impl IndexeddbCryptoStore { let name = format!("{prefix:0}::matrix-sdk-crypto"); // Open my_db v1 - let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, 1.1)?; + let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, 2.0)?; db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> { let old_version = evt.old_version(); @@ -168,8 +168,6 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::BACKUP_KEYS)?; - db.create_object_store(keys::ROOM_SETTINGS)?; - } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -183,6 +181,11 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::INBOUND_GROUP_SESSIONS)?; } + if old_version < 2.0 { + let db = evt.db(); + db.create_object_store(keys::ROOM_SETTINGS)?; + } + Ok(()) })); @@ -368,20 +371,11 @@ impl_crypto_store! { !changes.identities.new.is_empty() || !changes.identities.changed.is_empty(), keys::IDENTITIES, ), -<<<<<<< HEAD + (!changes.inbound_group_sessions.is_empty(), keys::INBOUND_GROUP_SESSIONS), (!changes.outbound_group_sessions.is_empty(), keys::OUTBOUND_GROUP_SESSIONS), (!changes.message_hashes.is_empty(), keys::OLM_HASHES), -||||||| parent of b0932a23c (Missing stores) - (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), - (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), - (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), -======= - (!changes.inbound_group_sessions.is_empty(), KEYS::INBOUND_GROUP_SESSIONS), - (!changes.outbound_group_sessions.is_empty(), KEYS::OUTBOUND_GROUP_SESSIONS), - (!changes.message_hashes.is_empty(), KEYS::OLM_HASHES), - (!changes.room_settings.is_empty(), KEYS::ROOM_SETTINGS), ->>>>>>> b0932a23c (Missing stores) + (!changes.room_settings.is_empty(), keys::ROOM_SETTINGS), ] .iter() .filter_map(|(id, key)| if *id { Some(*key) } else { None }) From 5bf91825493745acd82c7af15ca645cd776d1037 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:16:57 +0000 Subject: [PATCH 25/28] Fix issues after rebase --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 8 ++++---- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 6 +++--- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 5 +++++ crates/matrix-sdk-crypto/src/store/mod.rs | 5 ++++- .../matrix-sdk-indexeddb/src/crypto_store.rs | 19 +++++++++---------- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 12cb12fe8c5..78756c96791 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -812,7 +812,7 @@ mod uniffi_types { RequestVerificationResult, StartSasResult, Verification, VerificationRequest, }, BackupKeys, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, EncryptionSettings, - RoomKeyCounts, + EventEncryptionAlgorithm, RoomKeyCounts, RoomSettings, }; } @@ -945,7 +945,7 @@ mod test { let backup_keys = machine.get_backup_keys()?; assert!(backup_keys.is_some()); - let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org")?; + let settings1 = machine.get_room_settings("!AZkqtjvtwPAuyNOXEt:matrix.org".into())?; assert_eq!( Some(RoomSettings { algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, @@ -954,7 +954,7 @@ mod test { settings1 ); - let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org")?; + let settings2 = machine.get_room_settings("!CWLUCoEWXSFyTCOtfL:matrix.org".into())?; assert_eq!( Some(RoomSettings { algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2, @@ -963,7 +963,7 @@ mod test { settings2 ); - let settings3 = machine.get_room_settings("!XYZ:matrix.org")?; + let settings3 = machine.get_room_settings("!XYZ:matrix.org".into())?; assert!(settings3.is_none()); Ok(()) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index fbade20e9d2..bc218f42be3 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -572,7 +572,7 @@ impl OlmMachine { /// set_room_only_allow_trusted_devices) methods. pub fn get_room_settings( &self, - room_id: &str, + room_id: String, ) -> Result, CryptoStoreError> { let room_id = RoomId::parse(room_id)?; let settings = self @@ -587,7 +587,7 @@ impl OlmMachine { /// available variants pub fn set_room_algorithm( &self, - room_id: &str, + room_id: String, algorithm: EventEncryptionAlgorithm, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; @@ -614,7 +614,7 @@ impl OlmMachine { /// set_only_allow_trusted_devices) method. pub fn set_room_only_allow_trusted_devices( &self, - room_id: &str, + room_id: String, only_allow_trusted_devices: bool, ) -> Result<(), CryptoStoreError> { let room_id = RoomId::parse(room_id)?; diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 679e504e3cf..edaa4097c2f 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -467,3 +467,8 @@ dictionary PickledInboundGroupSession { boolean imported; boolean backed_up; }; + +dictionary RoomSettings { + EventEncryptionAlgorithm algorithm; + boolean only_allow_trusted_devices; +}; \ No newline at end of file diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 6564d6bc6bc..6d2e0ccfa46 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -653,7 +653,10 @@ impl Store { /// /// Any users not already on the list are flagged as awaiting a key query. /// Users that were already in the list are unaffected. - pub(crate) async fn update_tracked_users(&self, users: impl Iterator) -> Result<()> { + pub(crate) async fn update_tracked_users( + &self, + users: impl Iterator, + ) -> Result<()> { self.load_tracked_users().await?; let mut store_updates = Vec::new(); diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index 1d5c6cc6fbd..17a2b60149d 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -167,7 +167,6 @@ impl IndexeddbCryptoStore { db.create_object_store(keys::SECRET_REQUESTS_BY_INFO)?; db.create_object_store(keys::BACKUP_KEYS)?; - } else if old_version < 1.1 { // We changed how we store inbound group sessions, the key used to // be a trippled of `(room_id, sender_key, session_id)` now it's a @@ -550,10 +549,10 @@ impl_crypto_store! { } if !room_settings_changes.is_empty() { - let settings_store = tx.object_store(KEYS::ROOM_SETTINGS)?; + let settings_store = tx.object_store(keys::ROOM_SETTINGS)?; for (room_id, settings) in &room_settings_changes { - let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let key = self.encode_key(keys::ROOM_SETTINGS, room_id); let value = self.serialize_value(&settings)?; settings_store.put_key_val(&key, &value)?; } @@ -973,11 +972,11 @@ impl_crypto_store! { } async fn get_room_settings(&self, room_id: &RoomId) -> Result> { - let key = self.encode_key(KEYS::ROOM_SETTINGS, room_id); + let key = self.encode_key(keys::ROOM_SETTINGS, room_id); Ok(self .inner - .transaction_on_one_with_mode(KEYS::ROOM_SETTINGS, IdbTransactionMode::Readonly)? - .object_store(KEYS::ROOM_SETTINGS)? + .transaction_on_one_with_mode(keys::ROOM_SETTINGS, IdbTransactionMode::Readonly)? + .object_store(keys::ROOM_SETTINGS)? .get(&key)? .await? .map(|v| self.deserialize_value(v)) @@ -987,8 +986,8 @@ impl_crypto_store! { async fn get_custom_value(&self, key: &str) -> Result>> { Ok(self .inner - .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readonly)? - .object_store(KEYS::CORE)? + .transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readonly)? + .object_store(keys::CORE)? .get(&JsValue::from_str(key))? .await? .map(|v| self.deserialize_value(v)) @@ -998,8 +997,8 @@ impl_crypto_store! { async fn set_custom_value(&self, key: &str, value: Vec) -> Result<()> { self .inner - .transaction_on_one_with_mode(KEYS::CORE, IdbTransactionMode::Readwrite)? - .object_store(KEYS::CORE)? + .transaction_on_one_with_mode(keys::CORE, IdbTransactionMode::Readwrite)? + .object_store(keys::CORE)? .put_key_val(&JsValue::from_str(key), &self.serialize_value(&value)?)?; Ok(()) } From 0d082f6b96540bcf3bc814c297344f3976f0c2d8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:37:12 +0000 Subject: [PATCH 26/28] Update js tests --- bindings/matrix-sdk-crypto-js/tests/machine.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/matrix-sdk-crypto-js/tests/machine.test.js b/bindings/matrix-sdk-crypto-js/tests/machine.test.js index 7fd824c12d5..95c8ba24195 100644 --- a/bindings/matrix-sdk-crypto-js/tests/machine.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/machine.test.js @@ -51,7 +51,7 @@ describe(OlmMachine.name, () => { expect(databases).toHaveLength(2); expect(databases).toStrictEqual([ { name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 }, - { name: `${store_name}::matrix-sdk-crypto`, version: 1 }, + { name: `${store_name}::matrix-sdk-crypto`, version: 2 }, ]); // Creating a new Olm machine, with the stored state. @@ -142,7 +142,7 @@ describe(OlmMachine.name, () => { expect(databases).toHaveLength(2); expect(databases).toStrictEqual([ { name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 }, - { name: `${store_name}::matrix-sdk-crypto`, version: 1 }, + { name: `${store_name}::matrix-sdk-crypto`, version: 2 }, ]); // Let's force to close the `OlmMachine`. From 404b95ab848477a7717f526078f3aa8732a9a1b8 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Mar 2023 10:39:06 +0000 Subject: [PATCH 27/28] Missing migration method in UDL --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 78 +++++++++++----------- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 8 ++- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 78756c96791..3f872ede930 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -202,45 +202,6 @@ pub fn migrate( }) } -/// Migrate room settings, including room algorithm and whether to block -/// untrusted devices from legacy store to Sqlite store. -/// -/// Note that this method should only be used if a client has already migrated -/// account data via [migrate](#method.migrate) method, which did not include -/// room settings. For a brand new migration, the [migrate](#method.migrate) -/// method will take care of room settings automatically, if provided. -/// -/// # Arguments -/// -/// * `room_settings` - Map of room settings -/// -/// * `path` - The path where the Sqlite store should be created. -/// -/// * `passphrase` - The passphrase that should be used to encrypt the data at -/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store -/// and all its data will remain unencrypted. -pub fn migrate_room_settings( - room_settings: HashMap, - path: &str, - passphrase: Option, -) -> anyhow::Result<()> { - let runtime = Runtime::new()?; - runtime.block_on(async move { - let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; - - let mut rust_settings = HashMap::new(); - for (room_id, settings) in room_settings { - let room_id = RoomId::parse(room_id)?; - rust_settings.insert(room_id, settings.into()); - } - - let changes = Changes { room_settings: rust_settings, ..Default::default() }; - store.save_changes(changes).await?; - - Ok(()) - }) -} - async fn migrate_data( mut data: MigrationData, path: &str, @@ -519,6 +480,45 @@ fn collect_sessions( Ok((sessions, inbound_group_sessions)) } +/// Migrate room settings, including room algorithm and whether to block +/// untrusted devices from legacy store to Sqlite store. +/// +/// Note that this method should only be used if a client has already migrated +/// account data via [migrate](#method.migrate) method, which did not include +/// room settings. For a brand new migration, the [migrate](#method.migrate) +/// method will take care of room settings automatically, if provided. +/// +/// # Arguments +/// +/// * `room_settings` - Map of room settings +/// +/// * `path` - The path where the Sqlite store should be created. +/// +/// * `passphrase` - The passphrase that should be used to encrypt the data at +/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store +/// and all its data will remain unencrypted. +pub fn migrate_room_settings( + room_settings: HashMap, + path: &str, + passphrase: Option, +) -> anyhow::Result<()> { + let runtime = Runtime::new()?; + runtime.block_on(async move { + let store = SqliteCryptoStore::open(path, passphrase.as_deref()).await?; + + let mut rust_settings = HashMap::new(); + for (room_id, settings) in room_settings { + let room_id = RoomId::parse(room_id)?; + rust_settings.insert(room_id, settings.into()); + } + + let changes = Changes { room_settings: rust_settings, ..Default::default() }; + store.save_changes(changes).await?; + + Ok(()) + }) +} + /// Callback that will be passed over the FFI to report progress pub trait ProgressListener { /// The callback that should be called on the Rust side diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index edaa4097c2f..9f5e5fc88ce 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -14,6 +14,12 @@ namespace matrix_sdk_crypto_ffi { string? passphrase, ProgressListener progress_listener ); + [Throws=MigrationError] + void migrate_room_settings( + record room_settings, + [ByRef] string path, + string? passphrase + ); }; [Error] @@ -471,4 +477,4 @@ dictionary PickledInboundGroupSession { dictionary RoomSettings { EventEncryptionAlgorithm algorithm; boolean only_allow_trusted_devices; -}; \ No newline at end of file +}; From d226738d15a53daaa7f2213886c51d1947a58559 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 14 Mar 2023 10:42:15 +0000 Subject: [PATCH 28/28] More pub(crate) methods --- crates/matrix-sdk-crypto/src/store/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 6d2e0ccfa46..c780359a2a0 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -679,7 +679,7 @@ impl Store { /// from the `/sync` response. Any users *whose device lists we are /// tracking* are flagged as needing a key query. Users whose devices we /// are not tracking are ignored. - pub async fn mark_tracked_users_as_changed( + pub(crate) async fn mark_tracked_users_as_changed( &self, users: impl Iterator, ) -> Result<()> { @@ -703,7 +703,7 @@ impl Store { /// This is called after processing the response to a /keys/query request. /// Any users whose device lists we are tracking are removed from the /// list of those pending a /keys/query. - pub async fn mark_tracked_users_as_up_to_date( + pub(crate) async fn mark_tracked_users_as_up_to_date( &self, users: impl Iterator, sequence_number: SequenceNumber, @@ -765,7 +765,9 @@ impl Store { /// A pair `(users, sequence_number)`, where `users` is the list of users to /// be queried, and `sequence_number` is the current sequence number, /// which should be returned in `mark_tracked_users_as_up_to_date`. - pub(crate) async fn users_for_key_query(&self) -> Result<(HashSet, SequenceNumber)> { + pub(crate) async fn users_for_key_query( + &self, + ) -> Result<(HashSet, SequenceNumber)> { self.load_tracked_users().await?; Ok(self.users_for_key_query.lock().await.users_for_key_query())