1use std::{
16 cmp::max,
17 collections::{BTreeMap, BTreeSet},
18 fmt,
19 ops::Bound,
20 sync::{
21 Arc, RwLockReadGuard,
22 atomic::{AtomicBool, AtomicU64, Ordering},
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 DeviceId, OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId,
32 SecondsSinceUnixEpoch, TransactionId, UserId,
33 events::{
34 AnyMessageLikeEventContent,
35 room::{
36 encryption::{PossiblyRedactedRoomEncryptionEventContent, RoomEncryptionEventContent},
37 history_visibility::HistoryVisibility,
38 },
39 },
40 serde::Raw,
41};
42use serde::{Deserialize, Serialize};
43use tokio::sync::RwLock;
44use tracing::{debug, error, info};
45use vodozemac::{Curve25519PublicKey, megolm::SessionConfig};
46pub use vodozemac::{
47 PickleError,
48 megolm::{GroupSession, GroupSessionPickle, MegolmMessage, SessionKey},
49 olm::IdentityKeys,
50};
51
52use super::SessionCreationError;
53#[cfg(feature = "experimental-algorithms")]
54use crate::types::events::room::encrypted::MegolmV2AesSha2Content;
55use crate::{
56 DeviceData,
57 olm::account::shared_history_from_history_visibility,
58 session_manager::CollectStrategy,
59 store::caches::SequenceNumber,
60 types::{
61 EventEncryptionAlgorithm,
62 events::{
63 room::encrypted::{
64 MegolmV1AesSha2Content, RoomEncryptedEventContent, RoomEventEncryptionScheme,
65 },
66 room_key::{MegolmV1AesSha2Content as MegolmV1AesSha2RoomKeyContent, RoomKeyContent},
67 room_key_withheld::RoomKeyWithheldContent,
68 },
69 requests::ToDeviceRequest,
70 },
71};
72
73const ONE_HOUR: Duration = Duration::from_secs(60 * 60);
74const ONE_WEEK: Duration = Duration::from_secs(60 * 60 * 24 * 7);
75
76const ROTATION_PERIOD: Duration = ONE_WEEK;
77const ROTATION_MESSAGES: u64 = 100;
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
80pub(crate) enum ShareState {
82 NotShared,
84 SharedButChangedSenderKey,
87 Shared { message_index: u32, olm_wedging_index: SequenceNumber },
93}
94
95#[derive(Clone, Debug, Deserialize, Serialize)]
99pub struct EncryptionSettings {
100 pub algorithm: EventEncryptionAlgorithm,
102 #[cfg(feature = "experimental-encrypted-state-events")]
104 #[serde(default)]
105 pub encrypt_state_events: bool,
106 pub rotation_period: Duration,
108 pub rotation_period_msgs: u64,
110 pub history_visibility: HistoryVisibility,
112 #[serde(default)]
115 pub sharing_strategy: CollectStrategy,
116}
117
118impl Default for EncryptionSettings {
119 fn default() -> Self {
120 Self {
121 algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
122 #[cfg(feature = "experimental-encrypted-state-events")]
123 encrypt_state_events: false,
124 rotation_period: ROTATION_PERIOD,
125 rotation_period_msgs: ROTATION_MESSAGES,
126 history_visibility: HistoryVisibility::Shared,
127 sharing_strategy: CollectStrategy::default(),
128 }
129 }
130}
131
132impl EncryptionSettings {
133 pub fn new(
136 content: RoomEncryptionEventContent,
137 history_visibility: HistoryVisibility,
138 sharing_strategy: CollectStrategy,
139 ) -> Self {
140 let rotation_period: Duration =
141 content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
142 let rotation_period_msgs: u64 =
143 content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
144
145 Self {
146 algorithm: EventEncryptionAlgorithm::from(content.algorithm.as_str()),
147 #[cfg(feature = "experimental-encrypted-state-events")]
148 encrypt_state_events: false,
149 rotation_period,
150 rotation_period_msgs,
151 history_visibility,
152 sharing_strategy,
153 }
154 }
155
156 pub fn from_possibly_redacted(
162 content: PossiblyRedactedRoomEncryptionEventContent,
163 history_visibility: HistoryVisibility,
164 sharing_strategy: CollectStrategy,
165 ) -> Option<Self> {
166 let rotation_period: Duration =
167 content.rotation_period_ms.map_or(ROTATION_PERIOD, |r| Duration::from_millis(r.into()));
168 let rotation_period_msgs: u64 =
169 content.rotation_period_msgs.map_or(ROTATION_MESSAGES, Into::into);
170
171 Some(Self {
172 algorithm: EventEncryptionAlgorithm::from(content.algorithm?.as_str()),
173 #[cfg(feature = "experimental-encrypted-state-events")]
174 encrypt_state_events: false,
175 rotation_period,
176 rotation_period_msgs,
177 history_visibility,
178 sharing_strategy,
179 })
180 }
181}
182
183#[derive(Debug)]
187pub struct OutboundGroupSessionEncryptionResult {
188 pub content: Raw<RoomEncryptedEventContent>,
190 pub algorithm: EventEncryptionAlgorithm,
192 pub session_id: Arc<str>,
194}
195
196#[derive(Clone)]
202pub struct OutboundGroupSession {
203 inner: Arc<RwLock<GroupSession>>,
204 device_id: OwnedDeviceId,
205 account_identity_keys: Arc<IdentityKeys>,
206 session_id: Arc<str>,
207 room_id: OwnedRoomId,
208 pub(crate) creation_time: SecondsSinceUnixEpoch,
209 message_count: Arc<AtomicU64>,
210 shared: Arc<AtomicBool>,
211 invalidated: Arc<AtomicBool>,
212 settings: Arc<EncryptionSettings>,
213 shared_with_set: Arc<StdRwLock<ShareInfoSet>>,
214 to_share_with_set: Arc<StdRwLock<ToShareMap>>,
215}
216
217pub type ShareInfoSet = BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>;
222
223type ToShareMap = BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>;
224
225#[derive(Clone, Debug, Serialize, Deserialize)]
227pub enum ShareInfo {
228 Shared(SharedWith),
230 Withheld(WithheldCode),
232}
233
234impl ShareInfo {
235 pub fn new_shared(
237 sender_key: Curve25519PublicKey,
238 message_index: u32,
239 olm_wedging_index: SequenceNumber,
240 ) -> Self {
241 ShareInfo::Shared(SharedWith { sender_key, message_index, olm_wedging_index })
242 }
243
244 pub fn new_withheld(code: WithheldCode) -> Self {
246 ShareInfo::Withheld(code)
247 }
248}
249
250#[derive(Clone, Debug, Serialize, Deserialize)]
251pub struct SharedWith {
252 pub sender_key: Curve25519PublicKey,
254 pub message_index: u32,
256 #[serde(default)]
258 pub olm_wedging_index: SequenceNumber,
259}
260
261pub(crate) struct SharingView<'a> {
264 shared_with_set: RwLockReadGuard<'a, ShareInfoSet>,
265 to_share_with_set: RwLockReadGuard<'a, ToShareMap>,
266}
267
268impl SharingView<'_> {
269 pub(crate) fn get_share_state(&self, device: &DeviceData) -> ShareState {
272 self.iter_shares(Some(device.user_id()), Some(device.device_id()))
273 .map(|(_, _, info)| match info {
274 ShareInfo::Shared(info) => {
275 if device.curve25519_key() == Some(info.sender_key) {
276 ShareState::Shared {
277 message_index: info.message_index,
278 olm_wedging_index: info.olm_wedging_index,
279 }
280 } else {
281 ShareState::SharedButChangedSenderKey
282 }
283 }
284 ShareInfo::Withheld(_) => ShareState::NotShared,
285 })
286 .max()
289 .unwrap_or(ShareState::NotShared)
290 }
291
292 pub(crate) fn is_withheld_to(&self, device: &DeviceData, code: &WithheldCode) -> bool {
295 self.iter_shares(Some(device.user_id()), Some(device.device_id()))
296 .any(|(_, _, info)| matches!(info, ShareInfo::Withheld(c) if c == code))
297 }
298
299 pub(crate) fn iter_shares<'b, 'c>(
303 &self,
304 user_id: Option<&'b UserId>,
305 device_id: Option<&'c DeviceId>,
306 ) -> impl Iterator<Item = (&UserId, &DeviceId, &ShareInfo)> + use<'_, 'b, 'c> {
307 fn iter_share_info_set<'a, 'b, 'c>(
308 set: &'a ShareInfoSet,
309 user_ids: (Bound<&'b UserId>, Bound<&'b UserId>),
310 device_ids: (Bound<&'c DeviceId>, Bound<&'c DeviceId>),
311 ) -> impl Iterator<Item = (&'a UserId, &'a DeviceId, &'a ShareInfo)> + use<'a, 'b, 'c>
312 {
313 set.range::<UserId, _>(user_ids).flat_map(move |(uid, d)| {
314 d.range::<DeviceId, _>(device_ids)
315 .map(|(id, info)| (uid.as_ref(), id.as_ref(), info))
316 })
317 }
318
319 let user_ids = user_id
320 .map(|u| (Bound::Included(u), Bound::Included(u)))
321 .unwrap_or((Bound::Unbounded, Bound::Unbounded));
322 let device_ids = device_id
323 .map(|d| (Bound::Included(d), Bound::Included(d)))
324 .unwrap_or((Bound::Unbounded, Bound::Unbounded));
325
326 let already_shared = iter_share_info_set(&self.shared_with_set, user_ids, device_ids);
327 let pending = self
328 .to_share_with_set
329 .values()
330 .flat_map(move |(_, set)| iter_share_info_set(set, user_ids, device_ids));
331 already_shared.chain(pending)
332 }
333
334 pub(crate) fn shared_with_users(&self) -> impl Iterator<Item = &UserId> {
338 self.iter_shares(None, None).filter_map(|(u, _, info)| match info {
339 ShareInfo::Shared(_) => Some(u),
340 ShareInfo::Withheld(_) => None,
341 })
342 }
343}
344
345impl OutboundGroupSession {
346 pub(super) fn session_config(
347 algorithm: &EventEncryptionAlgorithm,
348 ) -> Result<SessionConfig, SessionCreationError> {
349 match algorithm {
350 EventEncryptionAlgorithm::MegolmV1AesSha2 => Ok(SessionConfig::version_1()),
351 #[cfg(feature = "experimental-algorithms")]
352 EventEncryptionAlgorithm::MegolmV2AesSha2 => Ok(SessionConfig::version_2()),
353 _ => Err(SessionCreationError::Algorithm(algorithm.to_owned())),
354 }
355 }
356
357 pub fn new(
371 device_id: OwnedDeviceId,
372 identity_keys: Arc<IdentityKeys>,
373 room_id: &RoomId,
374 settings: EncryptionSettings,
375 ) -> Result<Self, SessionCreationError> {
376 let config = Self::session_config(&settings.algorithm)?;
377
378 let session = GroupSession::new(config);
379 let session_id = session.session_id();
380
381 Ok(OutboundGroupSession {
382 inner: RwLock::new(session).into(),
383 room_id: room_id.into(),
384 device_id,
385 account_identity_keys: identity_keys,
386 session_id: session_id.into(),
387 creation_time: SecondsSinceUnixEpoch::now(),
388 message_count: Arc::new(AtomicU64::new(0)),
389 shared: Arc::new(AtomicBool::new(false)),
390 invalidated: Arc::new(AtomicBool::new(false)),
391 settings: Arc::new(settings),
392 shared_with_set: Default::default(),
393 to_share_with_set: Default::default(),
394 })
395 }
396
397 pub fn add_request(
407 &self,
408 request_id: OwnedTransactionId,
409 request: Arc<ToDeviceRequest>,
410 share_infos: ShareInfoSet,
411 ) {
412 self.to_share_with_set.write().insert(request_id, (request, share_infos));
413 }
414
415 pub fn withheld_code(&self, code: WithheldCode) -> RoomKeyWithheldContent {
418 RoomKeyWithheldContent::new(
419 self.settings().algorithm.to_owned(),
420 code,
421 self.room_id().to_owned(),
422 self.session_id().to_owned(),
423 self.sender_key().to_owned(),
424 self.device_id.clone(),
425 )
426 }
427
428 pub fn invalidate_session(&self) {
430 self.invalidated.store(true, Ordering::Relaxed)
431 }
432
433 pub fn settings(&self) -> &EncryptionSettings {
435 &self.settings
436 }
437
438 pub fn mark_request_as_sent(
443 &self,
444 request_id: &TransactionId,
445 ) -> BTreeMap<OwnedUserId, BTreeSet<OwnedDeviceId>> {
446 let mut no_olm_devices = BTreeMap::new();
447
448 let removed = self.to_share_with_set.write().remove(request_id);
449 if let Some((to_device, request)) = removed {
450 let recipients: BTreeMap<&UserId, BTreeSet<&DeviceId>> = request
451 .iter()
452 .map(|(u, d)| (u.as_ref(), d.keys().map(|d| d.as_ref()).collect()))
453 .collect();
454
455 info!(
456 ?request_id,
457 ?recipients,
458 ?to_device.event_type,
459 "Marking to-device request carrying a room key or a withheld as sent"
460 );
461
462 for (user_id, info) in request {
463 let no_olms: BTreeSet<OwnedDeviceId> = info
464 .iter()
465 .filter(|(_, info)| matches!(info, ShareInfo::Withheld(WithheldCode::NoOlm)))
466 .map(|(d, _)| d.to_owned())
467 .collect();
468 no_olm_devices.insert(user_id.to_owned(), no_olms);
469
470 self.shared_with_set.write().entry(user_id).or_default().extend(info);
471 }
472
473 if self.to_share_with_set.read().is_empty() {
474 debug!(
475 session_id = self.session_id(),
476 room_id = ?self.room_id,
477 "All m.room_key and withheld to-device requests were sent out, marking \
478 session as shared.",
479 );
480
481 self.mark_as_shared();
482 }
483 } else {
484 let request_ids: Vec<String> =
485 self.to_share_with_set.read().keys().map(|k| k.to_string()).collect();
486
487 error!(
488 all_request_ids = ?request_ids,
489 ?request_id,
490 "Marking to-device request carrying a room key as sent but no \
491 request found with the given id"
492 );
493 }
494
495 no_olm_devices
496 }
497
498 pub(crate) async fn encrypt_helper(&self, plaintext: String) -> MegolmMessage {
506 let mut session = self.inner.write().await;
507 self.message_count.fetch_add(1, Ordering::SeqCst);
508 session.encrypt(&plaintext)
509 }
510
511 async fn encrypt_inner<T: Serialize>(
525 &self,
526 payload: &T,
527 relates_to: Option<serde_json::Value>,
528 ) -> OutboundGroupSessionEncryptionResult {
529 let ciphertext = self
530 .encrypt_helper(
531 serde_json::to_string(payload).expect("payload serialization never fails"),
532 )
533 .await;
534 let scheme: RoomEventEncryptionScheme = match self.settings.algorithm {
535 EventEncryptionAlgorithm::MegolmV1AesSha2 => MegolmV1AesSha2Content {
536 ciphertext,
537 sender_key: Some(self.account_identity_keys.curve25519),
538 session_id: self.session_id().to_owned(),
539 device_id: Some(self.device_id.clone()),
540 }
541 .into(),
542 #[cfg(feature = "experimental-algorithms")]
543 EventEncryptionAlgorithm::MegolmV2AesSha2 => {
544 MegolmV2AesSha2Content { ciphertext, session_id: self.session_id().to_owned() }
545 .into()
546 }
547 _ => unreachable!(
548 "An outbound group session is always using one of the supported algorithms"
549 ),
550 };
551 let content = RoomEncryptedEventContent { scheme, relates_to, other: Default::default() };
552
553 OutboundGroupSessionEncryptionResult {
554 content: Raw::new(&content)
555 .expect("m.room.encrypted event content can always be serialized"),
556 algorithm: self.settings.algorithm.to_owned(),
557 session_id: self.session_id.clone(),
558 }
559 }
560
561 pub async fn encrypt(
578 &self,
579 event_type: &str,
580 content: &Raw<AnyMessageLikeEventContent>,
581 ) -> OutboundGroupSessionEncryptionResult {
582 #[derive(Serialize)]
583 struct Payload<'a> {
584 #[serde(rename = "type")]
585 event_type: &'a str,
586 content: &'a Raw<AnyMessageLikeEventContent>,
587 room_id: &'a RoomId,
588 }
589
590 let payload = Payload { event_type, content, room_id: &self.room_id };
591
592 let relates_to = content
593 .get_field::<serde_json::Value>("m.relates_to")
594 .expect("serde_json::Value deserialization with valid JSON input never fails");
595
596 self.encrypt_inner(&payload, relates_to).await
597 }
598
599 #[cfg(feature = "experimental-encrypted-state-events")]
619 pub async fn encrypt_state(
620 &self,
621 event_type: &str,
622 state_key: &str,
623 content: &Raw<AnyStateEventContent>,
624 ) -> Raw<RoomEncryptedEventContent> {
625 #[derive(Serialize)]
626 struct Payload<'a> {
627 #[serde(rename = "type")]
628 event_type: &'a str,
629 state_key: &'a str,
630 content: &'a Raw<AnyStateEventContent>,
631 room_id: &'a RoomId,
632 }
633
634 let payload = Payload { event_type, state_key, content, room_id: &self.room_id };
635 self.encrypt_inner(&payload, None).await.content
636 }
637
638 fn elapsed(&self) -> bool {
639 let creation_time = Duration::from_secs(self.creation_time.get().into());
640 let now = Duration::from_secs(SecondsSinceUnixEpoch::now().get().into());
641 now.checked_sub(creation_time)
642 .map(|elapsed| elapsed >= self.safe_rotation_period())
643 .unwrap_or(true)
644 }
645
646 fn safe_rotation_period(&self) -> Duration {
655 if cfg!(feature = "_disable-minimum-rotation-period-ms") {
656 self.settings.rotation_period
657 } else {
658 max(self.settings.rotation_period, ONE_HOUR)
659 }
660 }
661
662 pub fn expired(&self) -> bool {
667 let count = self.message_count.load(Ordering::SeqCst);
668 let rotation_period_msgs = self.settings.rotation_period_msgs.clamp(1, 10_000);
674
675 count >= rotation_period_msgs || self.elapsed()
676 }
677
678 pub fn invalidated(&self) -> bool {
680 self.invalidated.load(Ordering::Relaxed)
681 }
682
683 pub fn mark_as_shared(&self) {
688 self.shared.store(true, Ordering::Relaxed);
689 }
690
691 pub fn shared(&self) -> bool {
693 self.shared.load(Ordering::Relaxed)
694 }
695
696 pub async fn session_key(&self) -> SessionKey {
700 let session = self.inner.read().await;
701 session.session_key()
702 }
703
704 pub fn sender_key(&self) -> Curve25519PublicKey {
706 self.account_identity_keys.as_ref().curve25519.to_owned()
707 }
708
709 pub fn room_id(&self) -> &RoomId {
711 &self.room_id
712 }
713
714 pub fn session_id(&self) -> &str {
716 &self.session_id
717 }
718
719 pub async fn message_index(&self) -> u32 {
724 let session = self.inner.read().await;
725 session.message_index()
726 }
727
728 pub(crate) async fn as_content(&self) -> RoomKeyContent {
729 let session_key = self.session_key().await;
730 let shared_history =
731 shared_history_from_history_visibility(&self.settings.history_visibility);
732
733 RoomKeyContent::MegolmV1AesSha2(
734 MegolmV1AesSha2RoomKeyContent::new(
735 self.room_id().to_owned(),
736 self.session_id().to_owned(),
737 session_key,
738 shared_history,
739 )
740 .into(),
741 )
742 }
743
744 pub(crate) fn sharing_view(&self) -> SharingView<'_> {
748 SharingView {
749 shared_with_set: self.shared_with_set.read(),
750 to_share_with_set: self.to_share_with_set.read(),
751 }
752 }
753
754 #[cfg(test)]
757 pub fn mark_shared_with_from_index(
758 &self,
759 user_id: &UserId,
760 device_id: &DeviceId,
761 sender_key: Curve25519PublicKey,
762 index: u32,
763 ) {
764 self.shared_with_set.write().entry(user_id.to_owned()).or_default().insert(
765 device_id.to_owned(),
766 ShareInfo::new_shared(sender_key, index, Default::default()),
767 );
768 }
769
770 #[cfg(test)]
773 pub async fn mark_shared_with(
774 &self,
775 user_id: &UserId,
776 device_id: &DeviceId,
777 sender_key: Curve25519PublicKey,
778 ) {
779 let share_info =
780 ShareInfo::new_shared(sender_key, self.message_index().await, Default::default());
781 self.shared_with_set
782 .write()
783 .entry(user_id.to_owned())
784 .or_default()
785 .insert(device_id.to_owned(), share_info);
786 }
787
788 pub(crate) fn pending_requests(&self) -> Vec<Arc<ToDeviceRequest>> {
791 self.to_share_with_set.read().values().map(|(req, _)| req.clone()).collect()
792 }
793
794 pub(crate) fn pending_request_ids(&self) -> Vec<OwnedTransactionId> {
796 self.to_share_with_set.read().keys().cloned().collect()
797 }
798
799 pub fn from_pickle(
816 device_id: OwnedDeviceId,
817 identity_keys: Arc<IdentityKeys>,
818 pickle: PickledOutboundGroupSession,
819 ) -> Result<Self, PickleError> {
820 let inner: GroupSession = pickle.pickle.into();
821 let session_id = inner.session_id();
822
823 Ok(Self {
824 inner: Arc::new(RwLock::new(inner)),
825 device_id,
826 account_identity_keys: identity_keys,
827 session_id: session_id.into(),
828 room_id: pickle.room_id,
829 creation_time: pickle.creation_time,
830 message_count: AtomicU64::from(pickle.message_count).into(),
831 shared: AtomicBool::from(pickle.shared).into(),
832 invalidated: AtomicBool::from(pickle.invalidated).into(),
833 settings: pickle.settings,
834 shared_with_set: Arc::new(StdRwLock::new(pickle.shared_with_set)),
835 to_share_with_set: Arc::new(StdRwLock::new(pickle.requests)),
836 })
837 }
838
839 pub async fn pickle(&self) -> PickledOutboundGroupSession {
847 let pickle = self.inner.read().await.pickle();
848
849 PickledOutboundGroupSession {
850 pickle,
851 room_id: self.room_id.clone(),
852 settings: self.settings.clone(),
853 creation_time: self.creation_time,
854 message_count: self.message_count.load(Ordering::SeqCst),
855 shared: self.shared(),
856 invalidated: self.invalidated(),
857 shared_with_set: self.shared_with_set.read().clone(),
858 requests: self.to_share_with_set.read().clone(),
859 }
860 }
861}
862
863#[cfg(not(tarpaulin_include))]
864impl fmt::Debug for OutboundGroupSession {
865 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
866 f.debug_struct("OutboundGroupSession")
867 .field("session_id", &self.session_id)
868 .field("room_id", &self.room_id)
869 .field("creation_time", &self.creation_time)
870 .field("message_count", &self.message_count)
871 .finish()
872 }
873}
874
875#[derive(Deserialize, Serialize)]
880#[allow(missing_debug_implementations)]
881pub struct PickledOutboundGroupSession {
882 pub pickle: GroupSessionPickle,
884 pub settings: Arc<EncryptionSettings>,
886 pub room_id: OwnedRoomId,
888 pub creation_time: SecondsSinceUnixEpoch,
890 pub message_count: u64,
892 pub shared: bool,
894 pub invalidated: bool,
896 pub shared_with_set: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, ShareInfo>>,
898 pub requests: BTreeMap<OwnedTransactionId, (Arc<ToDeviceRequest>, ShareInfoSet)>,
900}
901
902#[cfg(test)]
903mod tests {
904 use std::time::Duration;
905
906 use ruma::{
907 EventEncryptionAlgorithm,
908 events::room::{
909 encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility,
910 },
911 uint,
912 };
913
914 use super::{EncryptionSettings, ROTATION_MESSAGES, ROTATION_PERIOD, ShareState};
915 use crate::CollectStrategy;
916
917 #[test]
918 fn test_encryption_settings_conversion() {
919 let mut content =
920 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
921 let settings = EncryptionSettings::new(
922 content.clone(),
923 HistoryVisibility::Joined,
924 CollectStrategy::AllDevices,
925 );
926
927 assert_eq!(settings.rotation_period, ROTATION_PERIOD);
928 assert_eq!(settings.rotation_period_msgs, ROTATION_MESSAGES);
929
930 content.rotation_period_ms = Some(uint!(3600));
931 content.rotation_period_msgs = Some(uint!(500));
932
933 let settings = EncryptionSettings::new(
934 content,
935 HistoryVisibility::Shared,
936 CollectStrategy::AllDevices,
937 );
938
939 assert_eq!(settings.rotation_period, Duration::from_millis(3600));
940 assert_eq!(settings.rotation_period_msgs, 500);
941 }
942
943 #[test]
946 fn test_share_state_ordering() {
947 let values = [
948 ShareState::NotShared,
949 ShareState::SharedButChangedSenderKey,
950 ShareState::Shared { message_index: 1, olm_wedging_index: Default::default() },
951 ];
952 match values[0] {
954 ShareState::NotShared
955 | ShareState::SharedButChangedSenderKey
956 | ShareState::Shared { .. } => {}
957 }
958 assert!(values.is_sorted());
959 }
960
961 #[cfg(any(target_os = "linux", target_os = "macos", target_family = "wasm"))]
962 mod expiration {
963 use std::{sync::atomic::Ordering, time::Duration};
964
965 use matrix_sdk_test::async_test;
966 use ruma::{
967 SecondsSinceUnixEpoch, device_id, events::room::message::RoomMessageEventContent,
968 room_id, serde::Raw, uint, user_id,
969 };
970
971 use crate::{
972 Account, EncryptionSettings, MegolmError,
973 olm::{OutboundGroupSession, SenderData},
974 };
975
976 const TWO_HOURS: Duration = Duration::from_secs(60 * 60 * 2);
977
978 #[async_test]
979 async fn test_session_is_not_expired_if_no_messages_sent_and_no_time_passed() {
980 let session = create_session(EncryptionSettings {
982 rotation_period_msgs: 1,
983 ..Default::default()
984 })
985 .await;
986
987 assert!(!session.expired());
991 }
992
993 #[async_test]
994 async fn test_session_is_expired_if_we_rotate_every_message_and_one_was_sent()
995 -> Result<(), MegolmError> {
996 let session = create_session(EncryptionSettings {
998 rotation_period_msgs: 1,
999 ..Default::default()
1000 })
1001 .await;
1002
1003 let _ = session
1005 .encrypt(
1006 "m.room.message",
1007 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
1008 )
1009 .await;
1010
1011 assert!(session.expired());
1013
1014 Ok(())
1015 }
1016
1017 #[async_test]
1018 async fn test_session_with_rotation_period_is_not_expired_after_no_time() {
1019 let session = create_session(EncryptionSettings {
1021 rotation_period: TWO_HOURS,
1022 ..Default::default()
1023 })
1024 .await;
1025
1026 assert!(!session.expired());
1030 }
1031
1032 #[async_test]
1033 async fn test_session_is_expired_after_rotation_period() {
1034 let mut session = create_session(EncryptionSettings {
1036 rotation_period: TWO_HOURS,
1037 ..Default::default()
1038 })
1039 .await;
1040
1041 let now = SecondsSinceUnixEpoch::now();
1043 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(10800));
1044
1045 assert!(session.expired());
1047 }
1048
1049 #[async_test]
1050 #[cfg(not(feature = "_disable-minimum-rotation-period-ms"))]
1051 async fn test_session_does_not_expire_under_one_hour_even_if_we_ask_for_shorter() {
1052 let mut session = create_session(EncryptionSettings {
1054 rotation_period: Duration::from_millis(100),
1055 ..Default::default()
1056 })
1057 .await;
1058
1059 let now = SecondsSinceUnixEpoch::now();
1061 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1062
1063 assert!(!session.expired());
1065
1066 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3601));
1068
1069 assert!(session.expired());
1071 }
1072
1073 #[async_test]
1074 #[cfg(feature = "_disable-minimum-rotation-period-ms")]
1075 async fn test_with_disable_minrotperiod_feature_sessions_can_expire_quickly() {
1076 let mut session = create_session(EncryptionSettings {
1078 rotation_period: Duration::from_millis(100),
1079 ..Default::default()
1080 })
1081 .await;
1082
1083 let now = SecondsSinceUnixEpoch::now();
1085 session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(1800));
1086
1087 assert!(session.expired());
1090 }
1091
1092 #[async_test]
1093 async fn test_session_with_zero_msgs_rotation_is_not_expired_initially() {
1094 let session = create_session(EncryptionSettings {
1096 rotation_period_msgs: 0,
1097 ..Default::default()
1098 })
1099 .await;
1100
1101 assert!(!session.expired());
1106 }
1107
1108 #[async_test]
1109 async fn test_session_with_zero_msgs_rotation_expires_after_one_message()
1110 -> Result<(), MegolmError> {
1111 let session = create_session(EncryptionSettings {
1113 rotation_period_msgs: 0,
1114 ..Default::default()
1115 })
1116 .await;
1117
1118 let _ = session
1120 .encrypt(
1121 "m.room.message",
1122 &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(),
1123 )
1124 .await;
1125
1126 assert!(session.expired());
1129
1130 Ok(())
1131 }
1132
1133 #[async_test]
1134 async fn test_session_expires_after_10k_messages_even_if_we_ask_for_more() {
1135 let session = create_session(EncryptionSettings {
1137 rotation_period_msgs: 100_000,
1138 ..Default::default()
1139 })
1140 .await;
1141
1142 assert!(!session.expired());
1144 session.message_count.store(1000, Ordering::SeqCst);
1145 assert!(!session.expired());
1146 session.message_count.store(9999, Ordering::SeqCst);
1147 assert!(!session.expired());
1148
1149 session.message_count.store(10_000, Ordering::SeqCst);
1151
1152 assert!(session.expired());
1155 }
1156
1157 async fn create_session(settings: EncryptionSettings) -> OutboundGroupSession {
1158 let account =
1159 Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID"))
1160 .static_data;
1161 let (session, _) = account
1162 .create_group_session_pair(
1163 room_id!("!test_room:example.org"),
1164 settings,
1165 SenderData::unknown(),
1166 )
1167 .await
1168 .unwrap();
1169 session
1170 }
1171 }
1172}