1use std::{
16 cmp::max,
17 collections::{BTreeMap, BTreeSet},
18 fmt,
19 sync::{
20 atomic::{AtomicBool, AtomicU64, Ordering},
21 Arc,
22 },
23 time::Duration,
24};
25
26use matrix_sdk_common::{deserialized_responses::WithheldCode, locks::RwLock as StdRwLock};
27use ruma::{
28 events::{
29 room::{encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility},
30 AnyMessageLikeEventContent,
31 },
32 serde::Raw,
33 DeviceId, OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId,
34 SecondsSinceUnixEpoch, TransactionId, UserId,
35};
36use serde::{Deserialize, Serialize};
37use tokio::sync::RwLock;
38use tracing::{debug, error, info};
39use vodozemac::{megolm::SessionConfig, Curve25519PublicKey};
40pub use vodozemac::{
41 megolm::{GroupSession, GroupSessionPickle, MegolmMessage, SessionKey},
42 olm::IdentityKeys,
43 PickleError,
44};
45
46use super::SessionCreationError;
47#[cfg(feature = "experimental-algorithms")]
48use crate::types::events::room::encrypted::MegolmV2AesSha2Content;
49use crate::{
50 session_manager::CollectStrategy,
51 store::caches::SequenceNumber,
52 types::{
53 events::{
54 room::encrypted::{
55 MegolmV1AesSha2Content, RoomEncryptedEventContent, RoomEventEncryptionScheme,
56 },
57 room_key::{MegolmV1AesSha2Content as MegolmV1AesSha2RoomKeyContent, RoomKeyContent},
58 room_key_withheld::RoomKeyWithheldContent,
59 },
60 requests::ToDeviceRequest,
61 EventEncryptionAlgorithm,
62 },
63 DeviceData,
64};
65
66const ONE_HOUR: Duration = Duration::from_secs(60 * 60);
67const ONE_WEEK: Duration = Duration::from_secs(60 * 60 * 24 * 7);
68
69const ROTATION_PERIOD: Duration = ONE_WEEK;
70const ROTATION_MESSAGES: u64 = 100;
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub(crate) enum ShareState {
75 NotShared,
77 SharedButChangedSenderKey,
80 Shared { message_index: u32, olm_wedging_index: SequenceNumber },
86}
87
88#[derive(Clone, Debug, Deserialize, Serialize)]
92pub struct EncryptionSettings {
93 pub algorithm: EventEncryptionAlgorithm,
95 pub rotation_period: Duration,
97 pub rotation_period_msgs: u64,
99 pub history_visibility: HistoryVisibility,
101 #[serde(default)]
104 pub sharing_strategy: CollectStrategy,
105}
106
107impl Default for EncryptionSettings {
108 fn default() -> Self {
109 Self {
110 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
111 rotation_period: ROTATION_PERIOD,
112 rotation_period_msgs: ROTATION_MESSAGES,
113 history_visibility: HistoryVisibility::Shared,
114 sharing_strategy: CollectStrategy::default(),
115 }
116 }
117}
118
119impl EncryptionSettings {
120 pub fn new(
123 content: RoomEncryptionEventContent,
124 history_visibility: HistoryVisibility,
125 sharing_strategy: CollectStrategy,
126 ) -> Self {
127 let rotation_period: Duration =
128 content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
129 let rotation_period_msgs: u64 =
130 content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
131
132 Self {
133 algorithm: EventEncryptionAlgorithm::from(content.algorithm.as_str()),
134 rotation_period,
135 rotation_period_msgs,
136 history_visibility,
137 sharing_strategy,
138 }
139 }
140}
141
142#[derive(Clone)]
148pub struct OutboundGroupSession {
149 inner: Arc<RwLock<GroupSession>>,
150 device_id: OwnedDeviceId,
151 account_identity_keys: Arc<IdentityKeys>,
152 session_id: Arc<str>,
153 room_id: OwnedRoomId,
154 pub(crate) creation_time: SecondsSinceUnixEpoch,
155 message_count: Arc<AtomicU64>,
156 shared: Arc<AtomicBool>,
157 invalidated: Arc<AtomicBool>,
158 settings: Arc<EncryptionSettings>,
159 pub(crate) shared_with_set:
160 Arc<StdRwLock<BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>>>,
161 #[allow(clippy::type_complexity)]
162 to_share_with_set:
163 Arc<StdRwLock<BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>>>,
164}
165
166pub type ShareInfoSet = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>;
171
172#[derive(Clone, Debug, Serialize, Deserialize)]
174pub enum ShareInfo {
175 Shared(SharedWith),
177 Withheld(WithheldCode),
179}
180
181impl ShareInfo {
182 pub fn new_shared(
184 sender_key: Curve25519PublicKey,
185 message_index: u32,
186 olm_wedging_index: SequenceNumber,
187 ) -> Self {
188 ShareInfo::Shared(SharedWith { sender_key, message_index, olm_wedging_index })
189 }
190
191 pub fn new_withheld(code: WithheldCode) -> Self {
193 ShareInfo::Withheld(code)
194 }
195}
196
197#[derive(Clone, Debug, Serialize, Deserialize)]
198pub struct SharedWith {
199 pub sender_key: Curve25519PublicKey,
201 pub message_index: u32,
203 #[serde(default)]
205 pub olm_wedging_index: SequenceNumber,
206}
207
208impl OutboundGroupSession {
209 pub(super) fn session_config(
210 algorithm: &EventEncryptionAlgorithm,
211 ) -> Result<SessionConfig, SessionCreationError> {
212 match algorithm {
213 EventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(SessionConfig::version_1()),
214 #[cfg(feature = "experimental-algorithms")]
215 EventEncryptionAlgorithm::MegolmV2AesSha2 => Ok(SessionConfig::version_2()),
216 _ => Err(SessionCreationError::Algorithm(algorithm.to_owned())),
217 }
218 }
219
220 pub fn new(
236 device_id: OwnedDeviceId,
237 identity_keys: Arc<IdentityKeys>,
238 room_id: &RoomId,
239 settings: EncryptionSettings,
240 ) -> Result<Self, SessionCreationError> {
241 let config = Self::session_config(&settings.algorithm)?;
242
243 let session = GroupSession::new(config);
244 let session_id = session.session_id();
245
246 Ok(OutboundGroupSession {
247 inner: RwLock::new(session).into(),
248 room_id: room_id.into(),
249 device_id,
250 account_identity_keys: identity_keys,
251 session_id: session_id.into(),
252 creation_time: SecondsSinceUnixEpoch::now(),
253 message_count: Arc::new(AtomicU64::new(0)),
254 shared: Arc::new(AtomicBool::new(false)),
255 invalidated: Arc::new(AtomicBool::new(false)),
256 settings: Arc::new(settings),
257 shared_with_set: Default::default(),
258 to_share_with_set: Default::default(),
259 })
260 }
261
262 pub fn add_request(
272 &self,
273 request_id: OwnedTransactionId,
274 request: Arc<ToDeviceRequest>,
275 share_infos: ShareInfoSet,
276 ) {
277 self.to_share_with_set.write().insert(request_id, (request, share_infos));
278 }
279
280 pub fn withheld_code(&self, code: WithheldCode) -> RoomKeyWithheldContent {
283 RoomKeyWithheldContent::new(
284 self.settings().algorithm.to_owned(),
285 code,
286 self.room_id().to_owned(),
287 self.session_id().to_owned(),
288 self.sender_key().to_owned(),
289 (*self.device_id).to_owned(),
290 )
291 }
292
293 pub fn invalidate_session(&self) {
295 self.invalidated.store(true, Ordering::Relaxed)
296 }
297
298 pub fn settings(&self) -> &EncryptionSettings {
300 &self.settings
301 }
302
303 pub fn mark_request_as_sent(
308 &self,
309 request_id: &TransactionId,
310 ) -> BTreeMap<OwnedUserId, BTreeSet<OwnedDeviceId>> {
311 let mut no_olm_devices = BTreeMap::new();
312
313 let removed = self.to_share_with_set.write().remove(request_id);
314 if let Some((to_device, request)) = removed {
315 let recipients: BTreeMap<&UserId, BTreeSet<&DeviceId>> = request
316 .iter()
317 .map(|(u, d)| (u.as_ref(), d.keys().map(|d| d.as_ref()).collect()))
318 .collect();
319
320 info!(
321 ?request_id,
322 ?recipients,
323 ?to_device.event_type,
324 "Marking to-device request carrying a room key or a withheld as sent"
325 );
326
327 for (user_id, info) in request {
328 let no_olms: BTreeSet<OwnedDeviceId> = info
329 .iter()
330 .filter(|(_, info)| matches!(info, ShareInfo::Withheld(WithheldCode::NoOlm)))
331 .map(|(d, _)| d.to_owned())
332 .collect();
333 no_olm_devices.insert(user_id.to_owned(), no_olms);
334
335 self.shared_with_set.write().entry(user_id).or_default().extend(info);
336 }
337
338 if self.to_share_with_set.read().is_empty() {
339 debug!(
340 session_id = self.session_id(),
341 room_id = ?self.room_id,
342 "All m.room_key and withheld to-device requests were sent out, marking \
343 session as shared.",
344 );
345
346 self.mark_as_shared();
347 }
348 } else {
349 let request_ids: Vec<String> =
350 self.to_share_with_set.read().keys().map(|k| k.to_string()).collect();
351
352 error!(
353 all_request_ids = ?request_ids,
354 request_id = ?request_id,
355 "Marking to-device request carrying a room key as sent but no \
356 request found with the given id"
357 );
358 }
359
360 no_olm_devices
361 }
362
363 pub(crate) async fn encrypt_helper(&self, plaintext: String) -> MegolmMessage {
371 let mut session = self.inner.write().await;
372 self.message_count.fetch_add(1, Ordering::SeqCst);
373 session.encrypt(&plaintext)
374 }
375
376 pub async fn encrypt(
393 &self,
394 event_type: &str,
395 content: &Raw<AnyMessageLikeEventContent>,
396 ) -> Raw<RoomEncryptedEventContent> {
397 #[derive(Serialize)]
398 struct Payload<'a> {
399 #[serde(rename = "type")]
400 event_type: &'a str,
401 content: &'a Raw<AnyMessageLikeEventContent>,
402 room_id: &'a RoomId,
403 }
404
405 let payload = Payload { event_type, content, room_id: &self.room_id };
406 let payload_json =
407 serde_json::to_string(&payload).expect("payload serialization never fails");
408
409 let relates_to = content
410 .get_field::<serde_json::Value>("m.relates_to")
411 .expect("serde_json::Value deserialization with valid JSON input never fails");
412
413 let ciphertext = self.encrypt_helper(payload_json).await;
414 let scheme: RoomEventEncryptionScheme = match self.settings.algorithm {
415 EventEncryptionAlgorithm::MegolmV1AesSha2 => MegolmV1AesSha2Content {
416 ciphertext,
417 sender_key: self.account_identity_keys.curve25519,
418 session_id: self.session_id().to_owned(),
419 device_id: (*self.device_id).to_owned(),
420 }
421 .into(),
422 #[cfg(feature = "experimental-algorithms")]
423 EventEncryptionAlgorithm::MegolmV2AesSha2 => {
424 MegolmV2AesSha2Content { ciphertext, session_id: self.session_id().to_owned() }
425 .into()
426 }
427 _ => unreachable!(
428 "An outbound group session is always using one of the supported algorithms"
429 ),
430 };
431
432 let content = RoomEncryptedEventContent { scheme, relates_to, other: Default::default() };
433
434 Raw::new(&content).expect("m.room.encrypted event content can always be serialized")
435 }
436
437 fn elapsed(&self) -> bool {
438 let creation_time = Duration::from_secs(self.creation_time.get().into());
439 let now = Duration::from_secs(SecondsSinceUnixEpoch::now().get().into());
440 now.checked_sub(creation_time)
441 .map(|elapsed| elapsed >= self.safe_rotation_period())
442 .unwrap_or(true)
443 }
444
445 fn safe_rotation_period(&self) -> Duration {
454 if cfg!(feature = "_disable-minimum-rotation-period-ms") {
455 self.settings.rotation_period
456 } else {
457 max(self.settings.rotation_period, ONE_HOUR)
458 }
459 }
460
461 pub fn expired(&self) -> bool {
466 let count = self.message_count.load(Ordering::SeqCst);
467 let rotation_period_msgs = self.settings.rotation_period_msgs.clamp(1, 10_000);
473
474 count >= rotation_period_msgs || self.elapsed()
475 }
476
477 pub fn invalidated(&self) -> bool {
479 self.invalidated.load(Ordering::Relaxed)
480 }
481
482 pub fn mark_as_shared(&self) {
487 self.shared.store(true, Ordering::Relaxed);
488 }
489
490 pub fn shared(&self) -> bool {
492 self.shared.load(Ordering::Relaxed)
493 }
494
495 pub async fn session_key(&self) -> SessionKey {
499 let session = self.inner.read().await;
500 session.session_key()
501 }
502
503 pub fn sender_key(&self) -> Curve25519PublicKey {
505 self.account_identity_keys.as_ref().curve25519.to_owned()
506 }
507
508 pub fn room_id(&self) -> &RoomId {
510 &self.room_id
511 }
512
513 pub fn session_id(&self) -> &str {
515 &self.session_id
516 }
517
518 pub async fn message_index(&self) -> u32 {
523 let session = self.inner.read().await;
524 session.message_index()
525 }
526
527 pub(crate) async fn as_content(&self) -> RoomKeyContent {
528 let session_key = self.session_key().await;
529
530 RoomKeyContent::MegolmV1AesSha2(
531 MegolmV1AesSha2RoomKeyContent::new(
532 self.room_id().to_owned(),
533 self.session_id().to_owned(),
534 session_key,
535 )
536 .into(),
537 )
538 }
539
540 pub(crate) fn is_shared_with(&self, device: &DeviceData) -> ShareState {
542 let shared_state = self.shared_with_set.read().get(device.user_id()).and_then(|d| {
544 d.get(device.device_id()).map(|s| match s {
545 ShareInfo::Shared(s) => {
546 if device.curve25519_key() == Some(s.sender_key) {
547 ShareState::Shared {
548 message_index: s.message_index,
549 olm_wedging_index: s.olm_wedging_index,
550 }
551 } else {
552 ShareState::SharedButChangedSenderKey
553 }
554 }
555 ShareInfo::Withheld(_) => ShareState::NotShared,
556 })
557 });
558
559 if let Some(state) = shared_state {
560 state
561 } else {
562 let shared = self.to_share_with_set.read().values().find_map(|(_, share_info)| {
568 let d = share_info.get(device.user_id())?;
569 let info = d.get(device.device_id())?;
570 Some(match info {
571 ShareInfo::Shared(info) => {
572 if device.curve25519_key() == Some(info.sender_key) {
573 ShareState::Shared {
574 message_index: info.message_index,
575 olm_wedging_index: info.olm_wedging_index,
576 }
577 } else {
578 ShareState::SharedButChangedSenderKey
579 }
580 }
581 ShareInfo::Withheld(_) => ShareState::NotShared,
582 })
583 });
584
585 shared.unwrap_or(ShareState::NotShared)
586 }
587 }
588
589 pub(crate) fn is_withheld_to(&self, device: &DeviceData, code: &WithheldCode) -> bool {
590 self.shared_with_set
591 .read()
592 .get(device.user_id())
593 .and_then(|d| {
594 let info = d.get(device.device_id())?;
595 Some(matches!(info, ShareInfo::Withheld(c) if c == code))
596 })
597 .unwrap_or_else(|| {
598 self.to_share_with_set.read().values().any(|(_, share_info)| {
604 share_info
605 .get(device.user_id())
606 .and_then(|d| d.get(device.device_id()))
607 .is_some_and(|info| matches!(info, ShareInfo::Withheld(c) if c == code))
608 })
609 })
610 }
611
612 #[cfg(test)]
615 pub fn mark_shared_with_from_index(
616 &self,
617 user_id: &UserId,
618 device_id: &DeviceId,
619 sender_key: Curve25519PublicKey,
620 index: u32,
621 ) {
622 self.shared_with_set.write().entry(user_id.to_owned()).or_default().insert(
623 device_id.to_owned(),
624 ShareInfo::new_shared(sender_key, index, Default::default()),
625 );
626 }
627
628 #[cfg(test)]
631 pub async fn mark_shared_with(
632 &self,
633 user_id: &UserId,
634 device_id: &DeviceId,
635 sender_key: Curve25519PublicKey,
636 ) {
637 let share_info =
638 ShareInfo::new_shared(sender_key, self.message_index().await, Default::default());
639 self.shared_with_set
640 .write()
641 .entry(user_id.to_owned())
642 .or_default()
643 .insert(device_id.to_owned(), share_info);
644 }
645
646 pub(crate) fn pending_requests(&self) -> Vec<Arc<ToDeviceRequest>> {
649 self.to_share_with_set.read().values().map(|(req, _)| req.clone()).collect()
650 }
651
652 pub(crate) fn pending_request_ids(&self) -> Vec<OwnedTransactionId> {
654 self.to_share_with_set.read().keys().cloned().collect()
655 }
656
657 pub fn from_pickle(
675 device_id: OwnedDeviceId,
676 identity_keys: Arc<IdentityKeys>,
677 pickle: PickledOutboundGroupSession,
678 ) -> Result<Self, PickleError> {
679 let inner: GroupSession = pickle.pickle.into();
680 let session_id = inner.session_id();
681
682 Ok(Self {
683 inner: Arc::new(RwLock::new(inner)),
684 device_id,
685 account_identity_keys: identity_keys,
686 session_id: session_id.into(),
687 room_id: pickle.room_id,
688 creation_time: pickle.creation_time,
689 message_count: AtomicU64::from(pickle.message_count).into(),
690 shared: AtomicBool::from(pickle.shared).into(),
691 invalidated: AtomicBool::from(pickle.invalidated).into(),
692 settings: pickle.settings,
693 shared_with_set: Arc::new(StdRwLock::new(pickle.shared_with_set)),
694 to_share_with_set: Arc::new(StdRwLock::new(pickle.requests)),
695 })
696 }
697
698 pub async fn pickle(&self) -> PickledOutboundGroupSession {
706 let pickle = self.inner.read().await.pickle();
707
708 PickledOutboundGroupSession {
709 pickle,
710 room_id: self.room_id.clone(),
711 settings: self.settings.clone(),
712 creation_time: self.creation_time,
713 message_count: self.message_count.load(Ordering::SeqCst),
714 shared: self.shared(),
715 invalidated: self.invalidated(),
716 shared_with_set: self.shared_with_set.read().clone(),
717 requests: self.to_share_with_set.read().clone(),
718 }
719 }
720}
721
722#[derive(Clone, Debug, Serialize, Deserialize)]
723pub struct OutboundGroupSessionPickle(String);
724
725impl From<String> for OutboundGroupSessionPickle {
726 fn from(p: String) -> Self {
727 Self(p)
728 }
729}
730
731#[cfg(not(tarpaulin_include))]
732impl fmt::Debug for OutboundGroupSession {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 f.debug_struct("OutboundGroupSession")
735 .field("session_id", &self.session_id)
736 .field("room_id", &self.room_id)
737 .field("creation_time", &self.creation_time)
738 .field("message_count", &self.message_count)
739 .finish()
740 }
741}
742
743#[derive(Deserialize, Serialize)]
748#[allow(missing_debug_implementations)]
749pub struct PickledOutboundGroupSession {
750 pub pickle: GroupSessionPickle,
752 pub settings: Arc<EncryptionSettings>,
754 pub room_id: OwnedRoomId,
756 pub creation_time: SecondsSinceUnixEpoch,
758 pub message_count: u64,
760 pub shared: bool,
762 pub invalidated: bool,
764 pub shared_with_set: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>,
766 pub requests: BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>,
768}
769
770#[cfg(test)]
771mod tests {
772 use std::time::Duration;
773
774 use ruma::{
775 events::room::{
776 encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility,
777 },
778 uint, EventEncryptionAlgorithm,
779 };
780
781 use super::{EncryptionSettings, ROTATION_MESSAGES, ROTATION_PERIOD};
782 use crate::CollectStrategy;
783
784 #[test]
785 fn test_encryption_settings_conversion() {
786 let mut content =
787 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
788 let settings = EncryptionSettings::new(
789 content.clone(),
790 HistoryVisibility::Joined,
791 CollectStrategy::AllDevices,
792 );
793
794 assert_eq!(settings.rotation_period, ROTATION_PERIOD);
795 assert_eq!(settings.rotation_period_msgs, ROTATION_MESSAGES);
796
797 content.rotation_period_ms = Some(uint!(3600));
798 content.rotation_period_msgs = Some(uint!(500));
799
800 let settings = EncryptionSettings::new(
801 content,
802 HistoryVisibility::Shared,
803 CollectStrategy::AllDevices,
804 );
805
806 assert_eq!(settings.rotation_period, Duration::from_millis(3600));
807 assert_eq!(settings.rotation_period_msgs, 500);
808 }
809
810 #[cfg(any(target_os = "linux", target_os = "macos", target_arch = "wasm32"))]
811 mod expiration {
812 use std::{sync::atomic::Ordering, time::Duration};
813
814 use matrix_sdk_test::async_test;
815 use ruma::{
816 device_id, events::room::message::RoomMessageEventContent, room_id, serde::Raw, uint,
817 user_id, SecondsSinceUnixEpoch,
818 };
819
820 use crate::{
821 olm::{OutboundGroupSession, SenderData},
822 Account, EncryptionSettings, MegolmError,
823 };
824
825 const TWO_HOURS: Duration = Duration::from_secs(60 * 60 * 2);
826
827 #[async_test]
828 async fn test_session_is_not_expired_if_no_messages_sent_and_no_time_passed() {
829 let session = create_session(EncryptionSettings {
831 rotation_period_msgs: 1,
832 ..Default::default()
833 })
834 .await;
835
836 assert!(!session.expired());
840 }
841
842 #[async_test]
843 async fn test_session_is_expired_if_we_rotate_every_message_and_one_was_sent(
844 ) -> Result<(), MegolmError> {
845 let session = create_session(EncryptionSettings {
847 rotation_period_msgs: 1,
848 ..Default::default()
849 })
850 .await;
851
852 let _ = session
854 .encrypt(
855 "m.room.message",
856 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
857 )
858 .await;
859
860 assert!(session.expired());
862
863 Ok(())
864 }
865
866 #[async_test]
867 async fn test_session_with_rotation_period_is_not_expired_after_no_time() {
868 let session = create_session(EncryptionSettings {
870 rotation_period: TWO_HOURS,
871 ..Default::default()
872 })
873 .await;
874
875 assert!(!session.expired());
879 }
880
881 #[async_test]
882 async fn test_session_is_expired_after_rotation_period() {
883 let mut session = create_session(EncryptionSettings {
885 rotation_period: TWO_HOURS,
886 ..Default::default()
887 })
888 .await;
889
890 let now = SecondsSinceUnixEpoch::now();
892 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(10800));
893
894 assert!(session.expired());
896 }
897
898 #[async_test]
899 #[cfg(not(feature = "_disable-minimum-rotation-period-ms"))]
900 async fn test_session_does_not_expire_under_one_hour_even_if_we_ask_for_shorter() {
901 let mut session = create_session(EncryptionSettings {
903 rotation_period: Duration::from_millis(100),
904 ..Default::default()
905 })
906 .await;
907
908 let now = SecondsSinceUnixEpoch::now();
910 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
911
912 assert!(!session.expired());
914
915 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3601));
917
918 assert!(session.expired());
920 }
921
922 #[async_test]
923 #[cfg(feature = "_disable-minimum-rotation-period-ms")]
924 async fn test_with_disable_minrotperiod_feature_sessions_can_expire_quickly() {
925 let mut session = create_session(EncryptionSettings {
927 rotation_period: Duration::from_millis(100),
928 ..Default::default()
929 })
930 .await;
931
932 let now = SecondsSinceUnixEpoch::now();
934 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
935
936 assert!(session.expired());
939 }
940
941 #[async_test]
942 async fn test_session_with_zero_msgs_rotation_is_not_expired_initially() {
943 let session = create_session(EncryptionSettings {
945 rotation_period_msgs: 0,
946 ..Default::default()
947 })
948 .await;
949
950 assert!(!session.expired());
955 }
956
957 #[async_test]
958 async fn test_session_with_zero_msgs_rotation_expires_after_one_message(
959 ) -> Result<(), MegolmError> {
960 let session = create_session(EncryptionSettings {
962 rotation_period_msgs: 0,
963 ..Default::default()
964 })
965 .await;
966
967 let _ = session
969 .encrypt(
970 "m.room.message",
971 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
972 )
973 .await;
974
975 assert!(session.expired());
978
979 Ok(())
980 }
981
982 #[async_test]
983 async fn test_session_expires_after_10k_messages_even_if_we_ask_for_more() {
984 let session = create_session(EncryptionSettings {
986 rotation_period_msgs: 100_000,
987 ..Default::default()
988 })
989 .await;
990
991 assert!(!session.expired());
993 session.message_count.store(1000, Ordering::SeqCst);
994 assert!(!session.expired());
995 session.message_count.store(9999, Ordering::SeqCst);
996 assert!(!session.expired());
997
998 session.message_count.store(10_000, Ordering::SeqCst);
1000
1001 assert!(session.expired());
1004 }
1005
1006 async fn create_session(settings: EncryptionSettings) -> OutboundGroupSession {
1007 let account =
1008 Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID"))
1009 .static_data;
1010 let (session, _) = account
1011 .create_group_session_pair(
1012 room_id!("!test_room:example.org"),
1013 settings,
1014 SenderData::unknown(),
1015 )
1016 .await
1017 .unwrap();
1018 session
1019 }
1020 }
1021}