Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce EncryptionState #4777

Merged
merged 7 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions bindings/matrix-sdk-ffi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,28 @@ Breaking changes:
- There is a new `abortOidcLogin` method that should be called if the webview is dismissed without a callback (
or fails to present).
- The rest of `AuthenticationError` is now found in the OidcError type.

- `OidcAuthenticationData` is now called `OidcAuthorizationData`.

- The `get_element_call_required_permissions` function now requires the device_id.

- `Room::is_encrypted` is replaced by `Room::latest_encryption_state`
which returns a value of the new `EncryptionState` enum; another
`Room::encryption_state` non-async and infallible method is added to get the
`EncryptionState` without running a network request.
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777)). One can
safely replace:

```rust
room.is_encrypted().await?
```

by

```rust
room.latest_encryption_state().await?.is_encrypted()
```

Additions:

- Add `Encryption::get_user_identity` which returns `UserIdentity`
Expand Down
14 changes: 11 additions & 3 deletions bindings/matrix-sdk-ffi/src/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use matrix_sdk::{
room::{
edit::EditedContent, power_levels::RoomPowerLevelChanges, Room as SdkRoom, RoomMemberRole,
},
ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType,
ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType, EncryptionState,
RoomHero as SdkRoomHero, RoomMemberships, RoomState,
};
use matrix_sdk_ui::timeline::{default_event_filter, RoomExt};
Expand Down Expand Up @@ -242,8 +242,16 @@ impl Room {
self.inner.room_id().to_string()
}

pub fn is_encrypted(&self) -> Result<bool, ClientError> {
Ok(RUNTIME.block_on(self.inner.is_encrypted())?)
pub fn encryption_state(&self) -> EncryptionState {
self.inner.encryption_state()
}

pub async fn latest_encryption_state(&self) -> Result<EncryptionState, ClientError> {
Ok(self.inner.latest_encryption_state().await?)
}

pub async fn is_encrypted(&self) -> Result<bool, ClientError> {
Ok(self.latest_encryption_state().await?.is_encrypted())
}

pub async fn members(&self) -> Result<Arc<RoomMembersIterator>, ClientError> {
Expand Down
6 changes: 5 additions & 1 deletion bindings/matrix-sdk-ffi/src/room_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,11 @@ impl RoomListItem {
/// **Note**: this info may not be reliable if you don't set up
/// `m.room.encryption` as required state.
async fn is_encrypted(&self) -> bool {
self.inner.is_encrypted().await.unwrap_or(false)
self.inner
.latest_encryption_state()
.await
.map(|state| state.is_encrypted())
.unwrap_or(false)
}

async fn latest_event(&self) -> Option<EventTimelineItem> {
Expand Down
5 changes: 5 additions & 0 deletions crates/matrix-sdk-base/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ All notable changes to this project will be documented in this file.
- `BaseClient` now has a `handle_verification_events` field which is `true` by
default and can be negated so the `NotificationClient` won't handle received
verification events too, causing errors in the `VerificationMachine`.
- [**breaking**] `Room::is_encryption_state_synced` has been removed
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777))
- [**breaking**] `Room::is_encrypted` is replaced by `Room::encryption_state`
which returns a value of the new `EncryptionState` enum
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777))

## [0.10.0] - 2025-02-04

Expand Down
6 changes: 3 additions & 3 deletions crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,9 +1081,9 @@ impl BaseClient {
let mut room_info = changes.room_infos.get(&room_id).unwrap().clone();

#[cfg(feature = "e2e-encryption")]
if room_info.is_encrypted() {
if room_info.encryption_state().is_encrypted() {
if let Some(o) = self.olm_machine().await.as_ref() {
if !room.is_encrypted() {
if !room.encryption_state().is_encrypted() {
// The room turned on encryption in this sync, we need
// to also get all the existing users and mark them for
// tracking.
Expand Down Expand Up @@ -1406,7 +1406,7 @@ impl BaseClient {
}

#[cfg(feature = "e2e-encryption")]
if room.is_encrypted() {
if room.encryption_state().is_encrypted() {
if let Some(o) = self.olm_machine().await.as_ref() {
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
}
Expand Down
6 changes: 3 additions & 3 deletions crates/matrix-sdk-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ pub use http;
pub use matrix_sdk_crypto as crypto;
pub use once_cell;
pub use rooms::{
apply_redaction, Room, RoomCreateWithCreatorEventContent, RoomDisplayName, RoomHero, RoomInfo,
RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMember, RoomMembersUpdate,
RoomMemberships, RoomState, RoomStateFilter,
apply_redaction, EncryptionState, Room, RoomCreateWithCreatorEventContent, RoomDisplayName,
RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMember,
RoomMembersUpdate, RoomMemberships, RoomState, RoomStateFilter,
};
pub use store::{
ComposerDraft, ComposerDraftType, QueueWedgeError, StateChanges, StateStore, StateStoreDataKey,
Expand Down
4 changes: 2 additions & 2 deletions crates/matrix-sdk-base/src/rooms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use std::{
use bitflags::bitflags;
pub use members::RoomMember;
pub use normal::{
apply_redaction, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
RoomMembersUpdate, RoomState, RoomStateFilter,
apply_redaction, EncryptionState, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate,
RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState, RoomStateFilter,
};
use regex::Regex;
use ruma::{
Expand Down
98 changes: 73 additions & 25 deletions crates/matrix-sdk-base/src/rooms/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,16 +426,6 @@ impl Room {
self.inner.read().sync_info != SyncInfo::NoState
}

/// Check if the room has its encryption event synced.
///
/// The encryption event can be missing when the room hasn't appeared in
/// sync yet.
///
/// Returns true if the encryption state is synced, false otherwise.
pub fn is_encryption_state_synced(&self) -> bool {
self.inner.read().encryption_state_synced
}

/// Get the `prev_batch` token that was received from the last sync. May be
/// `None` if the last sync contained the full room history.
pub fn last_prev_batch(&self) -> Option<String> {
Expand Down Expand Up @@ -531,9 +521,9 @@ impl Room {
self.inner.read().base_info.dm_targets.len()
}

/// Is the room encrypted.
pub fn is_encrypted(&self) -> bool {
self.inner.read().is_encrypted()
/// Get the encryption state of this room.
pub fn encryption_state(&self) -> EncryptionState {
self.inner.read().encryption_state()
}

/// Get the `m.room.encryption` content that enabled end to end encryption
Expand Down Expand Up @@ -1576,9 +1566,15 @@ impl RoomInfo {
self.room_state
}

/// Returns whether this is an encrypted room.
pub fn is_encrypted(&self) -> bool {
self.base_info.encryption.is_some()
/// Returns the encryption state of this room.
pub fn encryption_state(&self) -> EncryptionState {
if !self.encryption_state_synced {
EncryptionState::Unknown
} else if self.base_info.encryption.is_some() {
EncryptionState::Encrypted
} else {
EncryptionState::NotEncrypted
}
Comment on lines +1571 to +1577
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be nice to replace encryption_state_synced and base_info.encryptionwith EncryptionState where Encrypted keeps the m.room.encryption content as associated data.

But that's probably something for a different PR and might not even be worth the trouble.

Copy link
Member Author

@Hywan Hywan Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked about that in a meeting and agreed to address that in another PR.

}

/// Set the encryption event content in this room.
Expand All @@ -1596,9 +1592,7 @@ impl RoomInfo {
// then we can be certain that we have synced the encryption state event, so
// mark it here as synced.
if let AnySyncStateEvent::RoomEncryption(_) = event {
if self.is_encrypted() {
self.mark_encryption_state_synced();
}
self.mark_encryption_state_synced();
}

ret
Expand Down Expand Up @@ -2151,6 +2145,33 @@ fn compute_display_name_from_heroes(
}
}

/// Represents the state of a room encryption.
#[derive(Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum EncryptionState {
/// The room is encrypted.
Encrypted,

/// The room is not encrypted.
NotEncrypted,

/// The state of the room encryption is unknown, probably because the
/// `/sync` did not provide all data needed to decide.
Unknown,
}

impl EncryptionState {
/// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted].
pub fn is_encrypted(&self) -> bool {
matches!(self, Self::Encrypted)
}

/// Check whether `EncryptionState` is [`Unknown`][Self::Unknown].
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown)
}
}

#[cfg(test)]
mod tests {
use std::{
Expand All @@ -2161,6 +2182,7 @@ mod tests {
time::Duration,
};

use assert_matches::assert_matches;
use assign::assign;
use matrix_sdk_common::deserialized_responses::TimelineEvent;
use matrix_sdk_test::{
Expand Down Expand Up @@ -2197,7 +2219,10 @@ mod tests {
use similar_asserts::assert_eq;
use stream_assert::{assert_pending, assert_ready};

use super::{compute_display_name_from_heroes, Room, RoomHero, RoomInfo, RoomState, SyncInfo};
use super::{
compute_display_name_from_heroes, EncryptionState, Room, RoomHero, RoomInfo, RoomState,
SyncInfo,
};
use crate::{
latest_event::LatestEvent,
rooms::RoomNotableTags,
Expand Down Expand Up @@ -3567,11 +3592,10 @@ mod tests {
}

#[test]
fn test_encryption_is_set_when_encryption_event_is_received() {
fn test_encryption_is_set_when_encryption_event_is_received_encrypted() {
let (_store, room) = make_room_test_helper(RoomState::Joined);

assert!(room.is_encryption_state_synced().not());
assert!(room.is_encrypted().not());
assert_matches!(room.encryption_state(), EncryptionState::Unknown);

let encryption_content =
RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
Expand All @@ -3589,8 +3613,32 @@ mod tests {
));
receive_state_events(&room, vec![&encryption_event]);

assert!(room.is_encryption_state_synced());
assert!(room.is_encrypted());
assert_matches!(room.encryption_state(), EncryptionState::Encrypted);
}

#[test]
fn test_encryption_is_set_when_encryption_event_is_received_not_encrypted() {
let (_store, room) = make_room_test_helper(RoomState::Joined);

assert_matches!(room.encryption_state(), EncryptionState::Unknown);
room.inner.update_if(|info| {
info.mark_encryption_state_synced();

false
});

assert_matches!(room.encryption_state(), EncryptionState::NotEncrypted);
}

#[test]
fn test_encryption_state() {
assert!(EncryptionState::Unknown.is_unknown());
assert!(EncryptionState::Encrypted.is_unknown().not());
assert!(EncryptionState::NotEncrypted.is_unknown().not());

assert!(EncryptionState::Unknown.is_encrypted().not());
assert!(EncryptionState::Encrypted.is_encrypted());
assert!(EncryptionState::NotEncrypted.is_encrypted().not());
}

#[async_test]
Expand Down
4 changes: 2 additions & 2 deletions crates/matrix-sdk-base/src/sliding_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,9 @@ impl BaseClient {
.await;

#[cfg(feature = "e2e-encryption")]
if room_info.is_encrypted() {
if room_info.encryption_state().is_encrypted() {
if let Some(o) = self.olm_machine().await.as_ref() {
if !room.is_encrypted() {
if !room.encryption_state().is_encrypted() {
// The room turned on encryption in this sync, we need
// to also get all the existing users and mark them for
// tracking.
Expand Down
6 changes: 5 additions & 1 deletion crates/matrix-sdk-ui/src/notification_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,11 @@ impl NotificationItem {
room_avatar_url: room.avatar_url().map(|s| s.to_string()),
room_canonical_alias: room.canonical_alias().map(|c| c.to_string()),
is_direct_message_room: room.is_direct().await?,
is_room_encrypted: room.is_encrypted().await.ok(),
is_room_encrypted: room
.latest_encryption_state()
.await
.map(|state| state.is_encrypted())
.ok(),
joined_members_count: room.joined_members_count(),
is_noisy,
has_mention,
Expand Down
7 changes: 6 additions & 1 deletion crates/matrix-sdk-ui/src/timeline/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ impl TimelineBuilder {

let is_live = matches!(focus, TimelineFocus::Live);
let is_pinned_events = matches!(focus, TimelineFocus::PinnedEvents { .. });
let is_room_encrypted = room.is_encrypted().await.ok().unwrap_or_default();
let is_room_encrypted = room
.latest_encryption_state()
.await
.map(|state| state.is_encrypted())
.ok()
.unwrap_or_default();

let controller = TimelineController::new(
room,
Expand Down
4 changes: 2 additions & 2 deletions crates/matrix-sdk-ui/src/timeline/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,15 +394,15 @@ impl<P: RoomDataProvider, D: Decryptor> TimelineController<P, D> {
state.mark_all_events_as_encrypted();
};

if room_info.get().is_encrypted() {
if room_info.get().encryption_state().is_encrypted() {
// If the room was already encrypted, it won't toggle to unencrypted, so we can
// shut down this task early.
mark_encrypted().await;
return;
}

while let Some(info) = room_info.next().await {
if info.is_encrypted() {
if info.encryption_state().is_encrypted() {
mark_encrypted().await;
// Once the room is encrypted, it cannot switch back to unencrypted, so our work
// here is done.
Expand Down
25 changes: 21 additions & 4 deletions crates/matrix-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,34 @@ simpler methods:
this URI is not desirable, the `Oidc::fetch_account_management_url` method
can be used.
([#4663](https://github.com/matrix-org/matrix-rust-sdk/pull/4663))

- The `MediaRetentionPolicy` can now trigger regular cleanups with its new
`cleanup_frequency` setting.
([#4603](https://github.com/matrix-org/matrix-rust-sdk/pull/4603))
- [**breaking**] The HTTP client only allows TLS 1.2 or newer, as recommended by
[BCP 195](https://datatracker.ietf.org/doc/bcp195/).
([#4647](https://github.com/matrix-org/matrix-rust-sdk/pull/4647))
- Add `Room::report_room` api. ([#4713](https://github.com/matrix-org/matrix-rust-sdk/pull/4713))
- `Client::notification_client` will create a copy of the existing `Client`, but now it'll make sure
it doesn't handle any verification events to avoid an issue with these events being received and
processed twice if `NotificationProcessSetup` was `SingleSetup`.
- `Client::notification_client` will create a copy of the existing `Client`,
but now it'll make sure it doesn't handle any verification events to
avoid an issue with these events being received and processed twice if
`NotificationProcessSetup` was `SingleSetup`.
- [**breaking**] `Room::is_encrypted` is replaced by
`Room::latest_encryption_state` which returns a value of the new
`EncryptionState` enum; another `Room::encryption_state` non-async and
infallible method is added to get the `EncryptionState` without calling
`Room::request_encryption_state`. This latter method is also now public.
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777)). One can
safely replace:

```rust
room.is_encrypted().await?
```

by

```rust
room.latest_encryption_state().await?.is_encrypted()
```

### Bug Fixes

Expand Down
6 changes: 5 additions & 1 deletion crates/matrix-sdk/src/encryption/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1822,7 +1822,11 @@ mod tests {
client.base_client().receive_sync_response(response).await.unwrap();

let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
assert!(room.is_encrypted().await.expect("Getting encryption state"));
assert!(room
.latest_encryption_state()
.await
.expect("Getting encryption state")
.is_encrypted());

let event_id = event_id!("$1:example.org");
let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
Expand Down
Loading
Loading