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