1use std::{
16 cmp::max,
17 collections::{BTreeMap, BTreeSet},
18 fmt,
19 ops::Bound,
20 sync::{
21 atomic::{AtomicBool, AtomicU64, Ordering},
22 Arc, RwLockReadGuard,
23 },
24 time::Duration,
25};
26
27use matrix_sdk_common::{deserialized_responses::WithheldCode, locks::RwLock as StdRwLock};
28#[cfg(feature = "experimental-encrypted-state-events")]
29use ruma::events::AnyStateEventContent;
30use ruma::{
31 events::{
32 room::{encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility},
33 AnyMessageLikeEventContent,
34 },
35 serde::Raw,
36 DeviceId, OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId,
37 SecondsSinceUnixEpoch, TransactionId, UserId,
38};
39use serde::{Deserialize, Serialize};
40use tokio::sync::RwLock;
41use tracing::{debug, error, info};
42use vodozemac::{megolm::SessionConfig, Curve25519PublicKey};
43pub use vodozemac::{
44 megolm::{GroupSession, GroupSessionPickle, MegolmMessage, SessionKey},
45 olm::IdentityKeys,
46 PickleError,
47};
48
49use super::SessionCreationError;
50#[cfg(feature = "experimental-algorithms")]
51use crate::types::events::room::encrypted::MegolmV2AesSha2Content;
52use crate::{
53 olm::account::shared_history_from_history_visibility,
54 session_manager::CollectStrategy,
55 store::caches::SequenceNumber,
56 types::{
57 events::{
58 room::encrypted::{
59 MegolmV1AesSha2Content, RoomEncryptedEventContent, RoomEventEncryptionScheme,
60 },
61 room_key::{MegolmV1AesSha2Content as MegolmV1AesSha2RoomKeyContent, RoomKeyContent},
62 room_key_withheld::RoomKeyWithheldContent,
63 },
64 requests::ToDeviceRequest,
65 EventEncryptionAlgorithm,
66 },
67 DeviceData,
68};
69
70const ONE_HOUR: Duration = Duration::from_secs(60 * 60);
71const ONE_WEEK: Duration = Duration::from_secs(60 * 60 * 24 * 7);
72
73const ROTATION_PERIOD: Duration = ONE_WEEK;
74const ROTATION_MESSAGES: u64 = 100;
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
77pub(crate) enum ShareState {
79 NotShared,
81 SharedButChangedSenderKey,
84 Shared { message_index: u32, olm_wedging_index: SequenceNumber },
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize)]
96pub struct EncryptionSettings {
97 pub algorithm: EventEncryptionAlgorithm,
99 #[cfg(feature = "experimental-encrypted-state-events")]
101 #[serde(default)]
102 pub encrypt_state_events: bool,
103 pub rotation_period: Duration,
105 pub rotation_period_msgs: u64,
107 pub history_visibility: HistoryVisibility,
109 #[serde(default)]
112 pub sharing_strategy: CollectStrategy,
113}
114
115impl Default for EncryptionSettings {
116 fn default() -> Self {
117 Self {
118 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
119 #[cfg(feature = "experimental-encrypted-state-events")]
120 encrypt_state_events: false,
121 rotation_period: ROTATION_PERIOD,
122 rotation_period_msgs: ROTATION_MESSAGES,
123 history_visibility: HistoryVisibility::Shared,
124 sharing_strategy: CollectStrategy::default(),
125 }
126 }
127}
128
129impl EncryptionSettings {
130 pub fn new(
133 content: RoomEncryptionEventContent,
134 history_visibility: HistoryVisibility,
135 sharing_strategy: CollectStrategy,
136 ) -> Self {
137 let rotation_period: Duration =
138 content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
139 let rotation_period_msgs: u64 =
140 content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
141
142 Self {
143 algorithm: EventEncryptionAlgorithm::from(content.algorithm.as_str()),
144 #[cfg(feature = "experimental-encrypted-state-events")]
145 encrypt_state_events: false,
146 rotation_period,
147 rotation_period_msgs,
148 history_visibility,
149 sharing_strategy,
150 }
151 }
152}
153
154#[derive(Clone)]
160pub struct OutboundGroupSession {
161 inner: Arc<RwLock<GroupSession>>,
162 device_id: OwnedDeviceId,
163 account_identity_keys: Arc<IdentityKeys>,
164 session_id: Arc<str>,
165 room_id: OwnedRoomId,
166 pub(crate) creation_time: SecondsSinceUnixEpoch,
167 message_count: Arc<AtomicU64>,
168 shared: Arc<AtomicBool>,
169 invalidated: Arc<AtomicBool>,
170 settings: Arc<EncryptionSettings>,
171 shared_with_set: Arc<StdRwLock<ShareInfoSet>>,
172 to_share_with_set: Arc<StdRwLock<ToShareMap>>,
173}
174
175pub type ShareInfoSet = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>;
180
181type ToShareMap = BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>;
182
183#[derive(Clone, Debug, Serialize, Deserialize)]
185pub enum ShareInfo {
186 Shared(SharedWith),
188 Withheld(WithheldCode),
190}
191
192impl ShareInfo {
193 pub fn new_shared(
195 sender_key: Curve25519PublicKey,
196 message_index: u32,
197 olm_wedging_index: SequenceNumber,
198 ) -> Self {
199 ShareInfo::Shared(SharedWith { sender_key, message_index, olm_wedging_index })
200 }
201
202 pub fn new_withheld(code: WithheldCode) -> Self {
204 ShareInfo::Withheld(code)
205 }
206}
207
208#[derive(Clone, Debug, Serialize, Deserialize)]
209pub struct SharedWith {
210 pub sender_key: Curve25519PublicKey,
212 pub message_index: u32,
214 #[serde(default)]
216 pub olm_wedging_index: SequenceNumber,
217}
218
219pub(crate) struct SharingView<'a> {
222 shared_with_set: RwLockReadGuard<'a, ShareInfoSet>,
223 to_share_with_set: RwLockReadGuard<'a, ToShareMap>,
224}
225
226impl SharingView<'_> {
227 pub(crate) fn get_share_state(&self, device: &DeviceData) -> ShareState {
230 self.iter_shares(Some(device.user_id()), Some(device.device_id()))
231 .map(|(_, _, info)| match info {
232 ShareInfo::Shared(info) => {
233 if device.curve25519_key() == Some(info.sender_key) {
234 ShareState::Shared {
235 message_index: info.message_index,
236 olm_wedging_index: info.olm_wedging_index,
237 }
238 } else {
239 ShareState::SharedButChangedSenderKey
240 }
241 }
242 ShareInfo::Withheld(_) => ShareState::NotShared,
243 })
244 .max()
247 .unwrap_or(ShareState::NotShared)
248 }
249
250 pub(crate) fn is_withheld_to(&self, device: &DeviceData, code: &WithheldCode) -> bool {
253 self.iter_shares(Some(device.user_id()), Some(device.device_id()))
254 .any(|(_, _, info)| matches!(info, ShareInfo::Withheld(c) if c == code))
255 }
256
257 pub(crate) fn iter_shares<'b, 'c>(
261 &self,
262 user_id: Option<&'b UserId>,
263 device_id: Option<&'c DeviceId>,
264 ) -> impl Iterator<Item = (&UserId, &DeviceId, &ShareInfo)> + use<'_, 'b, 'c> {
265 fn iter_share_info_set<'a, 'b, 'c>(
266 set: &'a ShareInfoSet,
267 user_ids: (Bound<&'b UserId>, Bound<&'b UserId>),
268 device_ids: (Bound<&'c DeviceId>, Bound<&'c DeviceId>),
269 ) -> impl Iterator<Item = (&'a UserId, &'a DeviceId, &'a ShareInfo)> + use<'a, 'b, 'c>
270 {
271 set.range::<UserId, _>(user_ids).flat_map(move |(uid, d)| {
272 d.range::<DeviceId, _>(device_ids)
273 .map(|(id, info)| (uid.as_ref(), id.as_ref(), info))
274 })
275 }
276
277 let user_ids = user_id
278 .map(|u| (Bound::Included(u), Bound::Included(u)))
279 .unwrap_or((Bound::Unbounded, Bound::Unbounded));
280 let device_ids = device_id
281 .map(|d| (Bound::Included(d), Bound::Included(d)))
282 .unwrap_or((Bound::Unbounded, Bound::Unbounded));
283
284 let already_shared = iter_share_info_set(&self.shared_with_set, user_ids, device_ids);
285 let pending = self
286 .to_share_with_set
287 .values()
288 .flat_map(move |(_, set)| iter_share_info_set(set, user_ids, device_ids));
289 already_shared.chain(pending)
290 }
291
292 pub(crate) fn shared_with_users(&self) -> impl Iterator<Item = &UserId> {
296 self.iter_shares(None, None).filter_map(|(u, _, info)| match info {
297 ShareInfo::Shared(_) => Some(u),
298 ShareInfo::Withheld(_) => None,
299 })
300 }
301}
302
303impl OutboundGroupSession {
304 pub(super) fn session_config(
305 algorithm: &EventEncryptionAlgorithm,
306 ) -> Result<SessionConfig, SessionCreationError> {
307 match algorithm {
308 EventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(SessionConfig::version_1()),
309 #[cfg(feature = "experimental-algorithms")]
310 EventEncryptionAlgorithm::MegolmV2AesSha2 => Ok(SessionConfig::version_2()),
311 _ => Err(SessionCreationError::Algorithm(algorithm.to_owned())),
312 }
313 }
314
315 pub fn new(
331 device_id: OwnedDeviceId,
332 identity_keys: Arc<IdentityKeys>,
333 room_id: &RoomId,
334 settings: EncryptionSettings,
335 ) -> Result<Self, SessionCreationError> {
336 let config = Self::session_config(&settings.algorithm)?;
337
338 let session = GroupSession::new(config);
339 let session_id = session.session_id();
340
341 Ok(OutboundGroupSession {
342 inner: RwLock::new(session).into(),
343 room_id: room_id.into(),
344 device_id,
345 account_identity_keys: identity_keys,
346 session_id: session_id.into(),
347 creation_time: SecondsSinceUnixEpoch::now(),
348 message_count: Arc::new(AtomicU64::new(0)),
349 shared: Arc::new(AtomicBool::new(false)),
350 invalidated: Arc::new(AtomicBool::new(false)),
351 settings: Arc::new(settings),
352 shared_with_set: Default::default(),
353 to_share_with_set: Default::default(),
354 })
355 }
356
357 pub fn add_request(
367 &self,
368 request_id: OwnedTransactionId,
369 request: Arc<ToDeviceRequest>,
370 share_infos: ShareInfoSet,
371 ) {
372 self.to_share_with_set.write().insert(request_id, (request, share_infos));
373 }
374
375 pub fn withheld_code(&self, code: WithheldCode) -> RoomKeyWithheldContent {
378 RoomKeyWithheldContent::new(
379 self.settings().algorithm.to_owned(),
380 code,
381 self.room_id().to_owned(),
382 self.session_id().to_owned(),
383 self.sender_key().to_owned(),
384 (*self.device_id).to_owned(),
385 )
386 }
387
388 pub fn invalidate_session(&self) {
390 self.invalidated.store(true, Ordering::Relaxed)
391 }
392
393 pub fn settings(&self) -> &EncryptionSettings {
395 &self.settings
396 }
397
398 pub fn mark_request_as_sent(
403 &self,
404 request_id: &TransactionId,
405 ) -> BTreeMap<OwnedUserId, BTreeSet<OwnedDeviceId>> {
406 let mut no_olm_devices = BTreeMap::new();
407
408 let removed = self.to_share_with_set.write().remove(request_id);
409 if let Some((to_device, request)) = removed {
410 let recipients: BTreeMap<&UserId, BTreeSet<&DeviceId>> = request
411 .iter()
412 .map(|(u, d)| (u.as_ref(), d.keys().map(|d| d.as_ref()).collect()))
413 .collect();
414
415 info!(
416 ?request_id,
417 ?recipients,
418 ?to_device.event_type,
419 "Marking to-device request carrying a room key or a withheld as sent"
420 );
421
422 for (user_id, info) in request {
423 let no_olms: BTreeSet<OwnedDeviceId> = info
424 .iter()
425 .filter(|(_, info)| matches!(info, ShareInfo::Withheld(WithheldCode::NoOlm)))
426 .map(|(d, _)| d.to_owned())
427 .collect();
428 no_olm_devices.insert(user_id.to_owned(), no_olms);
429
430 self.shared_with_set.write().entry(user_id).or_default().extend(info);
431 }
432
433 if self.to_share_with_set.read().is_empty() {
434 debug!(
435 session_id = self.session_id(),
436 room_id = ?self.room_id,
437 "All m.room_key and withheld to-device requests were sent out, marking \
438 session as shared.",
439 );
440
441 self.mark_as_shared();
442 }
443 } else {
444 let request_ids: Vec<String> =
445 self.to_share_with_set.read().keys().map(|k| k.to_string()).collect();
446
447 error!(
448 all_request_ids = ?request_ids,
449 ?request_id,
450 "Marking to-device request carrying a room key as sent but no \
451 request found with the given id"
452 );
453 }
454
455 no_olm_devices
456 }
457
458 pub(crate) async fn encrypt_helper(&self, plaintext: String) -> MegolmMessage {
466 let mut session = self.inner.write().await;
467 self.message_count.fetch_add(1, Ordering::SeqCst);
468 session.encrypt(&plaintext)
469 }
470
471 async fn encrypt_inner<T: Serialize>(
485 &self,
486 payload: &T,
487 relates_to: Option<serde_json::Value>,
488 ) -> Raw<RoomEncryptedEventContent> {
489 let ciphertext = self
490 .encrypt_helper(
491 serde_json::to_string(payload).expect("payload serialization never fails"),
492 )
493 .await;
494 let scheme: RoomEventEncryptionScheme = match self.settings.algorithm {
495 EventEncryptionAlgorithm::MegolmV1AesSha2 => MegolmV1AesSha2Content {
496 ciphertext,
497 sender_key: Some(self.account_identity_keys.curve25519),
498 session_id: self.session_id().to_owned(),
499 device_id: Some(self.device_id.clone()),
500 }
501 .into(),
502 #[cfg(feature = "experimental-algorithms")]
503 EventEncryptionAlgorithm::MegolmV2AesSha2 => {
504 MegolmV2AesSha2Content { ciphertext, session_id: self.session_id().to_owned() }
505 .into()
506 }
507 _ => unreachable!(
508 "An outbound group session is always using one of the supported algorithms"
509 ),
510 };
511 let content = RoomEncryptedEventContent { scheme, relates_to, other: Default::default() };
512 Raw::new(&content).expect("m.room.encrypted event content can always be serialized")
513 }
514
515 pub async fn encrypt(
532 &self,
533 event_type: &str,
534 content: &Raw<AnyMessageLikeEventContent>,
535 ) -> Raw<RoomEncryptedEventContent> {
536 #[derive(Serialize)]
537 struct Payload<'a> {
538 #[serde(rename = "type")]
539 event_type: &'a str,
540 content: &'a Raw<AnyMessageLikeEventContent>,
541 room_id: &'a RoomId,
542 }
543
544 let payload = Payload { event_type, content, room_id: &self.room_id };
545
546 let relates_to = content
547 .get_field::<serde_json::Value>("m.relates_to")
548 .expect("serde_json::Value deserialization with valid JSON input never fails");
549
550 self.encrypt_inner(&payload, relates_to).await
551 }
552
553 #[cfg(feature = "experimental-encrypted-state-events")]
573 pub async fn encrypt_state(
574 &self,
575 event_type: &str,
576 state_key: &str,
577 content: &Raw<AnyStateEventContent>,
578 ) -> Raw<RoomEncryptedEventContent> {
579 #[derive(Serialize)]
580 struct Payload<'a> {
581 #[serde(rename = "type")]
582 event_type: &'a str,
583 state_key: &'a str,
584 content: &'a Raw<AnyStateEventContent>,
585 room_id: &'a RoomId,
586 }
587
588 let payload = Payload { event_type, state_key, content, room_id: &self.room_id };
589 self.encrypt_inner(&payload, None).await
590 }
591
592 fn elapsed(&self) -> bool {
593 let creation_time = Duration::from_secs(self.creation_time.get().into());
594 let now = Duration::from_secs(SecondsSinceUnixEpoch::now().get().into());
595 now.checked_sub(creation_time)
596 .map(|elapsed| elapsed >= self.safe_rotation_period())
597 .unwrap_or(true)
598 }
599
600 fn safe_rotation_period(&self) -> Duration {
609 if cfg!(feature = "_disable-minimum-rotation-period-ms") {
610 self.settings.rotation_period
611 } else {
612 max(self.settings.rotation_period, ONE_HOUR)
613 }
614 }
615
616 pub fn expired(&self) -> bool {
621 let count = self.message_count.load(Ordering::SeqCst);
622 let rotation_period_msgs = self.settings.rotation_period_msgs.clamp(1, 10_000);
628
629 count >= rotation_period_msgs || self.elapsed()
630 }
631
632 pub fn invalidated(&self) -> bool {
634 self.invalidated.load(Ordering::Relaxed)
635 }
636
637 pub fn mark_as_shared(&self) {
642 self.shared.store(true, Ordering::Relaxed);
643 }
644
645 pub fn shared(&self) -> bool {
647 self.shared.load(Ordering::Relaxed)
648 }
649
650 pub async fn session_key(&self) -> SessionKey {
654 let session = self.inner.read().await;
655 session.session_key()
656 }
657
658 pub fn sender_key(&self) -> Curve25519PublicKey {
660 self.account_identity_keys.as_ref().curve25519.to_owned()
661 }
662
663 pub fn room_id(&self) -> &RoomId {
665 &self.room_id
666 }
667
668 pub fn session_id(&self) -> &str {
670 &self.session_id
671 }
672
673 pub async fn message_index(&self) -> u32 {
678 let session = self.inner.read().await;
679 session.message_index()
680 }
681
682 pub(crate) async fn as_content(&self) -> RoomKeyContent {
683 let session_key = self.session_key().await;
684 let shared_history =
685 shared_history_from_history_visibility(&self.settings.history_visibility);
686
687 RoomKeyContent::MegolmV1AesSha2(
688 MegolmV1AesSha2RoomKeyContent::new(
689 self.room_id().to_owned(),
690 self.session_id().to_owned(),
691 session_key,
692 shared_history,
693 )
694 .into(),
695 )
696 }
697
698 pub(crate) fn sharing_view(&self) -> SharingView<'_> {
702 SharingView {
703 shared_with_set: self.shared_with_set.read(),
704 to_share_with_set: self.to_share_with_set.read(),
705 }
706 }
707
708 #[cfg(test)]
711 pub fn mark_shared_with_from_index(
712 &self,
713 user_id: &UserId,
714 device_id: &DeviceId,
715 sender_key: Curve25519PublicKey,
716 index: u32,
717 ) {
718 self.shared_with_set.write().entry(user_id.to_owned()).or_default().insert(
719 device_id.to_owned(),
720 ShareInfo::new_shared(sender_key, index, Default::default()),
721 );
722 }
723
724 #[cfg(test)]
727 pub async fn mark_shared_with(
728 &self,
729 user_id: &UserId,
730 device_id: &DeviceId,
731 sender_key: Curve25519PublicKey,
732 ) {
733 let share_info =
734 ShareInfo::new_shared(sender_key, self.message_index().await, Default::default());
735 self.shared_with_set
736 .write()
737 .entry(user_id.to_owned())
738 .or_default()
739 .insert(device_id.to_owned(), share_info);
740 }
741
742 pub(crate) fn pending_requests(&self) -> Vec<Arc<ToDeviceRequest>> {
745 self.to_share_with_set.read().values().map(|(req, _)| req.clone()).collect()
746 }
747
748 pub(crate) fn pending_request_ids(&self) -> Vec<OwnedTransactionId> {
750 self.to_share_with_set.read().keys().cloned().collect()
751 }
752
753 pub fn from_pickle(
771 device_id: OwnedDeviceId,
772 identity_keys: Arc<IdentityKeys>,
773 pickle: PickledOutboundGroupSession,
774 ) -> Result<Self, PickleError> {
775 let inner: GroupSession = pickle.pickle.into();
776 let session_id = inner.session_id();
777
778 Ok(Self {
779 inner: Arc::new(RwLock::new(inner)),
780 device_id,
781 account_identity_keys: identity_keys,
782 session_id: session_id.into(),
783 room_id: pickle.room_id,
784 creation_time: pickle.creation_time,
785 message_count: AtomicU64::from(pickle.message_count).into(),
786 shared: AtomicBool::from(pickle.shared).into(),
787 invalidated: AtomicBool::from(pickle.invalidated).into(),
788 settings: pickle.settings,
789 shared_with_set: Arc::new(StdRwLock::new(pickle.shared_with_set)),
790 to_share_with_set: Arc::new(StdRwLock::new(pickle.requests)),
791 })
792 }
793
794 pub async fn pickle(&self) -> PickledOutboundGroupSession {
802 let pickle = self.inner.read().await.pickle();
803
804 PickledOutboundGroupSession {
805 pickle,
806 room_id: self.room_id.clone(),
807 settings: self.settings.clone(),
808 creation_time: self.creation_time,
809 message_count: self.message_count.load(Ordering::SeqCst),
810 shared: self.shared(),
811 invalidated: self.invalidated(),
812 shared_with_set: self.shared_with_set.read().clone(),
813 requests: self.to_share_with_set.read().clone(),
814 }
815 }
816}
817
818#[cfg(not(tarpaulin_include))]
819impl fmt::Debug for OutboundGroupSession {
820 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
821 f.debug_struct("OutboundGroupSession")
822 .field("session_id", &self.session_id)
823 .field("room_id", &self.room_id)
824 .field("creation_time", &self.creation_time)
825 .field("message_count", &self.message_count)
826 .finish()
827 }
828}
829
830#[derive(Deserialize, Serialize)]
835#[allow(missing_debug_implementations)]
836pub struct PickledOutboundGroupSession {
837 pub pickle: GroupSessionPickle,
839 pub settings: Arc<EncryptionSettings>,
841 pub room_id: OwnedRoomId,
843 pub creation_time: SecondsSinceUnixEpoch,
845 pub message_count: u64,
847 pub shared: bool,
849 pub invalidated: bool,
851 pub shared_with_set: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>,
853 pub requests: BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>,
855}
856
857#[cfg(test)]
858mod tests {
859 use std::time::Duration;
860
861 use ruma::{
862 events::room::{
863 encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility,
864 },
865 uint, EventEncryptionAlgorithm,
866 };
867
868 use super::{EncryptionSettings, ShareState, ROTATION_MESSAGES, ROTATION_PERIOD};
869 use crate::CollectStrategy;
870
871 #[test]
872 fn test_encryption_settings_conversion() {
873 let mut content =
874 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
875 let settings = EncryptionSettings::new(
876 content.clone(),
877 HistoryVisibility::Joined,
878 CollectStrategy::AllDevices,
879 );
880
881 assert_eq!(settings.rotation_period, ROTATION_PERIOD);
882 assert_eq!(settings.rotation_period_msgs, ROTATION_MESSAGES);
883
884 content.rotation_period_ms = Some(uint!(3600));
885 content.rotation_period_msgs = Some(uint!(500));
886
887 let settings = EncryptionSettings::new(
888 content,
889 HistoryVisibility::Shared,
890 CollectStrategy::AllDevices,
891 );
892
893 assert_eq!(settings.rotation_period, Duration::from_millis(3600));
894 assert_eq!(settings.rotation_period_msgs, 500);
895 }
896
897 #[test]
900 fn test_share_state_ordering() {
901 let values = [
902 ShareState::NotShared,
903 ShareState::SharedButChangedSenderKey,
904 ShareState::Shared { message_index: 1, olm_wedging_index: Default::default() },
905 ];
906 match values[0] {
908 ShareState::NotShared
909 | ShareState::SharedButChangedSenderKey
910 | ShareState::Shared { .. } => {}
911 }
912 assert!(values.is_sorted());
913 }
914
915 #[cfg(any(target_os = "linux", target_os = "macos", target_family = "wasm"))]
916 mod expiration {
917 use std::{sync::atomic::Ordering, time::Duration};
918
919 use matrix_sdk_test::async_test;
920 use ruma::{
921 device_id, events::room::message::RoomMessageEventContent, room_id, serde::Raw, uint,
922 user_id, SecondsSinceUnixEpoch,
923 };
924
925 use crate::{
926 olm::{OutboundGroupSession, SenderData},
927 Account, EncryptionSettings, MegolmError,
928 };
929
930 const TWO_HOURS: Duration = Duration::from_secs(60 * 60 * 2);
931
932 #[async_test]
933 async fn test_session_is_not_expired_if_no_messages_sent_and_no_time_passed() {
934 let session = create_session(EncryptionSettings {
936 rotation_period_msgs: 1,
937 ..Default::default()
938 })
939 .await;
940
941 assert!(!session.expired());
945 }
946
947 #[async_test]
948 async fn test_session_is_expired_if_we_rotate_every_message_and_one_was_sent(
949 ) -> Result<(), MegolmError> {
950 let session = create_session(EncryptionSettings {
952 rotation_period_msgs: 1,
953 ..Default::default()
954 })
955 .await;
956
957 let _ = session
959 .encrypt(
960 "m.room.message",
961 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
962 )
963 .await;
964
965 assert!(session.expired());
967
968 Ok(())
969 }
970
971 #[async_test]
972 async fn test_session_with_rotation_period_is_not_expired_after_no_time() {
973 let session = create_session(EncryptionSettings {
975 rotation_period: TWO_HOURS,
976 ..Default::default()
977 })
978 .await;
979
980 assert!(!session.expired());
984 }
985
986 #[async_test]
987 async fn test_session_is_expired_after_rotation_period() {
988 let mut session = create_session(EncryptionSettings {
990 rotation_period: TWO_HOURS,
991 ..Default::default()
992 })
993 .await;
994
995 let now = SecondsSinceUnixEpoch::now();
997 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(10800));
998
999 assert!(session.expired());
1001 }
1002
1003 #[async_test]
1004 #[cfg(not(feature = "_disable-minimum-rotation-period-ms"))]
1005 async fn test_session_does_not_expire_under_one_hour_even_if_we_ask_for_shorter() {
1006 let mut session = create_session(EncryptionSettings {
1008 rotation_period: Duration::from_millis(100),
1009 ..Default::default()
1010 })
1011 .await;
1012
1013 let now = SecondsSinceUnixEpoch::now();
1015 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1016
1017 assert!(!session.expired());
1019
1020 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3601));
1022
1023 assert!(session.expired());
1025 }
1026
1027 #[async_test]
1028 #[cfg(feature = "_disable-minimum-rotation-period-ms")]
1029 async fn test_with_disable_minrotperiod_feature_sessions_can_expire_quickly() {
1030 let mut session = create_session(EncryptionSettings {
1032 rotation_period: Duration::from_millis(100),
1033 ..Default::default()
1034 })
1035 .await;
1036
1037 let now = SecondsSinceUnixEpoch::now();
1039 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1040
1041 assert!(session.expired());
1044 }
1045
1046 #[async_test]
1047 async fn test_session_with_zero_msgs_rotation_is_not_expired_initially() {
1048 let session = create_session(EncryptionSettings {
1050 rotation_period_msgs: 0,
1051 ..Default::default()
1052 })
1053 .await;
1054
1055 assert!(!session.expired());
1060 }
1061
1062 #[async_test]
1063 async fn test_session_with_zero_msgs_rotation_expires_after_one_message(
1064 ) -> Result<(), MegolmError> {
1065 let session = create_session(EncryptionSettings {
1067 rotation_period_msgs: 0,
1068 ..Default::default()
1069 })
1070 .await;
1071
1072 let _ = session
1074 .encrypt(
1075 "m.room.message",
1076 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
1077 )
1078 .await;
1079
1080 assert!(session.expired());
1083
1084 Ok(())
1085 }
1086
1087 #[async_test]
1088 async fn test_session_expires_after_10k_messages_even_if_we_ask_for_more() {
1089 let session = create_session(EncryptionSettings {
1091 rotation_period_msgs: 100_000,
1092 ..Default::default()
1093 })
1094 .await;
1095
1096 assert!(!session.expired());
1098 session.message_count.store(1000, Ordering::SeqCst);
1099 assert!(!session.expired());
1100 session.message_count.store(9999, Ordering::SeqCst);
1101 assert!(!session.expired());
1102
1103 session.message_count.store(10_000, Ordering::SeqCst);
1105
1106 assert!(session.expired());
1109 }
1110
1111 async fn create_session(settings: EncryptionSettings) -> OutboundGroupSession {
1112 let account =
1113 Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID"))
1114 .static_data;
1115 let (session, _) = account
1116 .create_group_session_pair(
1117 room_id!("!test_room:example.org"),
1118 settings,
1119 SenderData::unknown(),
1120 )
1121 .await
1122 .unwrap();
1123 session
1124 }
1125 }
1126}