From 3c1e28e42ac56a5e1d8232a29f039e814afdd562 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Fri, 8 Mar 2024 10:36:29 +0000 Subject: [PATCH] crypto: Break up the expiration tests for clarity --- .../src/olm/group_sessions/outbound.rs | 209 ++++++++++++------ 1 file changed, 145 insertions(+), 64 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs index b75fd08201e..25d4021b246 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs @@ -735,20 +735,16 @@ pub struct PickledOutboundGroupSession { #[cfg(test)] mod tests { - use std::{sync::atomic::Ordering, time::Duration}; + use std::time::Duration; - use matrix_sdk_test::async_test; use ruma::{ - device_id, events::room::{ encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility, - message::RoomMessageEventContent, }, - room_id, uint, user_id, EventEncryptionAlgorithm, + uint, EventEncryptionAlgorithm, }; use super::{EncryptionSettings, ROTATION_MESSAGES, ROTATION_PERIOD}; - use crate::{Account, MegolmError}; #[test] fn test_encryption_settings_conversion() { @@ -768,78 +764,163 @@ mod tests { assert_eq!(settings.rotation_period_msgs, 500); } - #[async_test] #[cfg(any(target_os = "linux", target_os = "macos", target_arch = "wasm32"))] - async fn test_expiration() -> Result<(), MegolmError> { - use ruma::{serde::Raw, SecondsSinceUnixEpoch}; - - let settings = EncryptionSettings { rotation_period_msgs: 1, ..Default::default() }; - - let account = - Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID")) - .static_data; - let (session, _) = account - .create_group_session_pair(room_id!("!test_room:example.org"), settings) - .await - .unwrap(); - - assert!(!session.expired()); - let _ = session - .encrypt( - "m.room.message", - &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(), - ) - .await; - assert!(session.expired()); + mod expiration { + use std::{sync::atomic::Ordering, time::Duration}; - let settings = EncryptionSettings { - rotation_period: Duration::from_millis(100), - ..Default::default() + use matrix_sdk_test::async_test; + use ruma::{ + device_id, events::room::message::RoomMessageEventContent, room_id, serde::Raw, uint, + user_id, SecondsSinceUnixEpoch, }; - let (mut session, _) = account - .create_group_session_pair(room_id!("!test_room:example.org"), settings) - .await - .unwrap(); + use crate::{olm::OutboundGroupSession, Account, EncryptionSettings, MegolmError}; + + #[async_test] + async fn session_is_not_expired_if_no_messages_sent_and_no_time_passed() { + // Given a session that expires after one message + let session = create_session(EncryptionSettings { + rotation_period_msgs: 1, + ..Default::default() + }) + .await; - assert!(!session.expired()); + // When we send no messages at all - let now = SecondsSinceUnixEpoch::now(); - session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3600)); - assert!(session.expired()); + // Then it is not expired + assert!(!session.expired()); + } - let settings = EncryptionSettings { rotation_period_msgs: 0, ..Default::default() }; + #[async_test] + async fn session_is_expired_if_we_rotate_every_message_and_one_was_sent( + ) -> Result<(), MegolmError> { + // Given a session that expires after one message + let session = create_session(EncryptionSettings { + rotation_period_msgs: 1, + ..Default::default() + }) + .await; - let (session, _) = account - .create_group_session_pair(room_id!("!test_room:example.org"), settings) - .await - .unwrap(); + // When we send a message + let _ = session + .encrypt( + "m.room.message", + &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(), + ) + .await; - assert!(!session.expired()); + // Then the session is expired + assert!(session.expired()); - let _ = session - .encrypt( - "m.room.message", - &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(), - ) + Ok(()) + } + + #[async_test] + async fn session_with_short_rotation_period_is_not_expired_after_no_time() { + // Given a session with a 100ms expiration + let session = create_session(EncryptionSettings { + rotation_period: Duration::from_millis(100), + ..Default::default() + }) .await; - assert!(session.expired()); - let settings = EncryptionSettings { rotation_period_msgs: 100_000, ..Default::default() }; + // When we don't allow any time to pass + + // Then it is not expired + assert!(!session.expired()); + } + + #[async_test] + async fn session_is_expired_after_rotation_period() { + // Given a session with a 100ms expiration + let mut session = create_session(EncryptionSettings { + rotation_period: Duration::from_millis(100), + ..Default::default() + }) + .await; - let (session, _) = account - .create_group_session_pair(room_id!("!test_room:example.org"), settings) - .await - .unwrap(); + // When one hour has passed + let now = SecondsSinceUnixEpoch::now(); + session.creation_time = SecondsSinceUnixEpoch(now.get() - uint!(3600)); - assert!(!session.expired()); - session.message_count.store(1000, Ordering::SeqCst); - assert!(!session.expired()); - session.message_count.store(9999, Ordering::SeqCst); - assert!(!session.expired()); - session.message_count.store(10_000, Ordering::SeqCst); - assert!(session.expired()); + // Then the session is expired + assert!(session.expired()); + } - Ok(()) + #[async_test] + async fn session_with_zero_msgs_rotation_is_not_expired_initially() { + // Given a session that is supposed to expire after zero messages + let session = create_session(EncryptionSettings { + rotation_period_msgs: 0, + ..Default::default() + }) + .await; + + // When we send no messages + + // Then the session is not expired: we are protected against this nonsensical + // setup + assert!(!session.expired()); + } + + #[async_test] + async fn session_with_zero_msgs_rotation_expires_after_one_message( + ) -> Result<(), MegolmError> { + // Given a session that is supposed to expire after zero messages + let session = create_session(EncryptionSettings { + rotation_period_msgs: 0, + ..Default::default() + }) + .await; + + // When we send a message + let _ = session + .encrypt( + "m.room.message", + &Raw::new(&RoomMessageEventContent::text_plain("Test message"))?.cast(), + ) + .await; + + // Then the session is expired: we treated rotation_period_msgs=0 as if it were + // =1 + assert!(session.expired()); + + Ok(()) + } + + #[async_test] + async fn session_expires_after_10k_messages_even_if_we_ask_for_more() { + // Given we asked to expire after 100K messages + let session = create_session(EncryptionSettings { + rotation_period_msgs: 100_000, + ..Default::default() + }) + .await; + + // Sanity: it does not expire after <10K messages + assert!(!session.expired()); + session.message_count.store(1000, Ordering::SeqCst); + assert!(!session.expired()); + session.message_count.store(9999, Ordering::SeqCst); + assert!(!session.expired()); + + // When we have sent >= 10K messages + session.message_count.store(10_000, Ordering::SeqCst); + + // Then it is considered expired: we enforce a maximum of 10K messages before + // rotation. + assert!(session.expired()); + } + + async fn create_session(settings: EncryptionSettings) -> OutboundGroupSession { + let account = + Account::with_device_id(user_id!("@alice:example.org"), device_id!("DEVICEID")) + .static_data; + let (session, _) = account + .create_group_session_pair(room_id!("!test_room:example.org"), settings) + .await + .unwrap(); + session + } } }