Skip to main content

matrix_sdk_base/room/
encryption.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use ruma::events::room::encryption::PossiblyRedactedRoomEncryptionEventContent;
16
17use super::Room;
18
19impl Room {
20    /// Get the encryption state of this room.
21    pub fn encryption_state(&self) -> EncryptionState {
22        self.info.read().encryption_state()
23    }
24
25    /// Get the `m.room.encryption` content that enabled end to end encryption
26    /// in the room.
27    pub fn encryption_settings(&self) -> Option<PossiblyRedactedRoomEncryptionEventContent> {
28        self.info.read().base_info.encryption.clone()
29    }
30}
31
32/// Represents the state of a room encryption.
33#[derive(Debug)]
34#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
35pub enum EncryptionState {
36    /// The room is encrypted.
37    Encrypted,
38
39    /// The room is encrypted, additionally requiring state events to be
40    /// encrypted.
41    #[cfg(feature = "experimental-encrypted-state-events")]
42    StateEncrypted,
43
44    /// The room is not encrypted.
45    NotEncrypted,
46
47    /// The state of the room encryption is unknown, probably because the
48    /// `/sync` did not provide all data needed to decide.
49    Unknown,
50}
51
52impl EncryptionState {
53    /// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted].
54    #[cfg(not(feature = "experimental-encrypted-state-events"))]
55    pub fn is_encrypted(&self) -> bool {
56        matches!(self, Self::Encrypted)
57    }
58
59    /// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted] or
60    /// [`StateEncrypted`][Self::StateEncrypted].
61    #[cfg(feature = "experimental-encrypted-state-events")]
62    pub fn is_encrypted(&self) -> bool {
63        matches!(self, Self::Encrypted | Self::StateEncrypted)
64    }
65
66    /// Check whether `EncryptionState` is
67    /// [`StateEncrypted`][Self::StateEncrypted].
68    #[cfg(feature = "experimental-encrypted-state-events")]
69    pub fn is_state_encrypted(&self) -> bool {
70        matches!(self, Self::StateEncrypted)
71    }
72
73    /// Check whether `EncryptionState` is [`Unknown`][Self::Unknown].
74    pub fn is_unknown(&self) -> bool {
75        matches!(self, Self::Unknown)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use std::{
82        ops::{Not, Sub},
83        sync::Arc,
84        time::Duration,
85    };
86
87    use assert_matches::assert_matches;
88    use matrix_sdk_test::{ALICE, event_factory::EventFactory};
89    use ruma::{
90        EventEncryptionAlgorithm, MilliSecondsSinceUnixEpoch, event_id,
91        events::{AnySyncStateEvent, room::encryption::RoomEncryptionEventContent},
92        room_id,
93        serde::Raw,
94        time::SystemTime,
95        user_id,
96    };
97
98    use super::{EncryptionState, Room};
99    use crate::{
100        RoomState,
101        store::{MemoryStore, SaveLockedStateStore},
102        utils::RawStateEventWithKeys,
103    };
104
105    fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
106        let store = Arc::new(MemoryStore::new());
107        let user_id = user_id!("@me:example.org");
108        let room_id = room_id!("!test:localhost");
109        let (sender, _receiver) = tokio::sync::broadcast::channel(1);
110
111        (
112            store.clone(),
113            Room::new(user_id, SaveLockedStateStore::new(store), room_id, room_type, sender),
114        )
115    }
116
117    fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
118        MilliSecondsSinceUnixEpoch::from_system_time(
119            SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
120        )
121        .expect("date out of range")
122    }
123
124    fn receive_state_events(room: &Room, events: Vec<Raw<AnySyncStateEvent>>) {
125        room.info.update_if(|info| {
126            let mut res = false;
127            for ev in events {
128                res |= info.handle_state_event(
129                    &mut RawStateEventWithKeys::try_from_raw_state_event(ev)
130                        .expect("generated state event should be valid"),
131                );
132            }
133            res
134        });
135    }
136
137    #[test]
138    fn test_encryption_is_set_when_encryption_event_is_received_encrypted() {
139        let (_store, room) = make_room_test_helper(RoomState::Joined);
140
141        assert_matches!(room.encryption_state(), EncryptionState::Unknown);
142
143        let encryption_content =
144            RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
145        let encryption_event = EventFactory::new()
146            .sender(*ALICE)
147            .event(encryption_content)
148            .state_key("")
149            .event_id(event_id!("$1234_1"))
150            // we can simply use now here since this will be dropped when using a MinimalStateEvent
151            // in the roomInfo
152            .server_ts(timestamp(0))
153            .into();
154        receive_state_events(&room, vec![encryption_event]);
155
156        assert_matches!(room.encryption_state(), EncryptionState::Encrypted);
157    }
158
159    #[test]
160    fn test_encryption_is_set_when_encryption_event_is_received_not_encrypted() {
161        let (_store, room) = make_room_test_helper(RoomState::Joined);
162
163        assert_matches!(room.encryption_state(), EncryptionState::Unknown);
164        room.info.update_if(|info| {
165            info.mark_encryption_state_synced();
166
167            false
168        });
169
170        assert_matches!(room.encryption_state(), EncryptionState::NotEncrypted);
171    }
172
173    #[test]
174    fn test_encryption_state() {
175        assert!(EncryptionState::Unknown.is_unknown());
176        assert!(EncryptionState::Encrypted.is_unknown().not());
177        assert!(EncryptionState::NotEncrypted.is_unknown().not());
178
179        assert!(EncryptionState::Unknown.is_encrypted().not());
180        assert!(EncryptionState::Encrypted.is_encrypted());
181        assert!(EncryptionState::NotEncrypted.is_encrypted().not());
182
183        #[cfg(feature = "experimental-encrypted-state-events")]
184        {
185            assert!(EncryptionState::StateEncrypted.is_unknown().not());
186            assert!(EncryptionState::StateEncrypted.is_encrypted());
187
188            assert!(EncryptionState::Unknown.is_state_encrypted().not());
189            assert!(EncryptionState::Encrypted.is_state_encrypted().not());
190            assert!(EncryptionState::StateEncrypted.is_state_encrypted());
191            assert!(EncryptionState::NotEncrypted.is_state_encrypted().not());
192        }
193    }
194}