From 6b394d96bd3e02162cd51bd0bcfdaeb3034b47b1 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 20 Mar 2024 12:13:11 +0000 Subject: [PATCH 1/3] crypto: Formatting for integration_tests --- .../src/store/integration_tests.rs | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/store/integration_tests.rs b/crates/matrix-sdk-crypto/src/store/integration_tests.rs index 1ed29ecbd..f93828da8 100644 --- a/crates/matrix-sdk-crypto/src/store/integration_tests.rs +++ b/crates/matrix-sdk-crypto/src/store/integration_tests.rs @@ -3,45 +3,41 @@ macro_rules! cryptostore_integration_tests { () => { mod cryptostore_integration_tests { - use std::time::Duration; use std::collections::{BTreeMap, HashMap}; + use std::time::Duration; use assert_matches::assert_matches; use matrix_sdk_test::async_test; use ruma::{ - device_id, - events::secret::request::SecretName, - room_id, - serde::Raw, - to_device::DeviceIdOrAllDevices, - user_id, DeviceId, RoomId, TransactionId, UserId + device_id, events::secret::request::SecretName, room_id, serde::Raw, + to_device::DeviceIdOrAllDevices, user_id, DeviceId, RoomId, TransactionId, UserId, }; use serde_json::value::to_raw_value; use $crate::{ olm::{ - Curve25519PublicKey, InboundGroupSession, OlmMessageHash, - PrivateCrossSigningIdentity, Account, Session, + Account, Curve25519PublicKey, InboundGroupSession, OlmMessageHash, + PrivateCrossSigningIdentity, Session, }, store::{ - Changes, CryptoStore, DeviceChanges, - GossipRequest, IdentityChanges, BackupDecryptionKey, RoomSettings, PendingChanges, + BackupDecryptionKey, Changes, CryptoStore, DeviceChanges, GossipRequest, + IdentityChanges, PendingChanges, RoomSettings, }, testing::{get_device, get_other_identity, get_own_identity}, types::{ events::{ dummy::DummyEventContent, + olm_v1::{DecryptedSecretSendEvent, OlmV1Keys}, room_key_request::MegolmV1AesSha2Content, room_key_withheld::{ CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent, WithheldCode, }, - olm_v1::{DecryptedSecretSendEvent, OlmV1Keys}, secret_send::SecretSendContent, ToDeviceEvent, }, EventEncryptionAlgorithm, }, - ReadOnlyDevice, SecretInfo, ToDeviceRequest, TrackedUser, GossippedSecret, + GossippedSecret, ReadOnlyDevice, SecretInfo, ToDeviceRequest, TrackedUser, }; use super::get_store; @@ -82,13 +78,12 @@ macro_rules! cryptostore_integration_tests { bob.generate_one_time_keys(1); let one_time_key = *bob.one_time_keys().values().next().unwrap(); let sender_key = bob.identity_keys().curve25519; - let session = alice - .create_outbound_session_helper( - Default::default(), - sender_key, - one_time_key, - false, - ); + let session = alice.create_outbound_session_helper( + Default::default(), + sender_key, + one_time_key, + false, + ); (alice, session) } @@ -101,7 +96,7 @@ macro_rules! cryptostore_integration_tests { let account = get_account(); store - .save_pending_changes(PendingChanges { account: Some(account), }) + .save_pending_changes(PendingChanges { account: Some(account) }) .await .expect("Can't save account"); assert!(store.get_static_account().is_some()); @@ -114,7 +109,10 @@ macro_rules! cryptostore_integration_tests { assert!(store.load_account().await.unwrap().is_none()); let account = get_account(); - store.save_pending_changes(PendingChanges { account: Some(account), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account) }) + .await + .expect("Can't save account"); assert!(store.get_static_account().is_some()); } @@ -123,7 +121,10 @@ macro_rules! cryptostore_integration_tests { let store = get_store("load_account", None).await; let account = get_account(); - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account.deep_clone()) }) + .await + .expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); @@ -137,7 +138,10 @@ macro_rules! cryptostore_integration_tests { get_store("load_account_with_passphrase", Some("secret_passphrase")).await; let account = get_account(); - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account.deep_clone()) }) + .await + .expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); @@ -150,12 +154,18 @@ macro_rules! cryptostore_integration_tests { let store = get_store("save_and_share_account", None).await; let mut account = get_account(); - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account.deep_clone()) }) + .await + .expect("Can't save account"); account.mark_as_shared(); account.update_uploaded_key_count(50); - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account.deep_clone()) }) + .await + .expect("Can't save account"); let loaded_account = store.load_account().await.expect("Can't load account"); let loaded_account = loaded_account.unwrap(); @@ -168,7 +178,10 @@ macro_rules! cryptostore_integration_tests { async fn load_sessions() { let store = get_store("load_sessions", None).await; let (account, session) = get_account_and_session().await; - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { account: Some(account.deep_clone()) }) + .await + .expect("Can't save account"); let changes = Changes { sessions: vec![session.clone()], ..Default::default() }; @@ -195,7 +208,12 @@ macro_rules! cryptostore_integration_tests { let sender_key = session.sender_key.to_base64(); let session_id = session.session_id().to_owned(); - store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account"); + store + .save_pending_changes(PendingChanges { + account: Some(account.deep_clone()), + }) + .await + .expect("Can't save account"); let changes = Changes { sessions: vec![session.clone()], ..Default::default() }; store.save_changes(changes).await.unwrap(); @@ -236,7 +254,8 @@ macro_rules! cryptostore_integration_tests { "Initially there should be no outbound group session" ); - let (session, _) = account.create_group_session_pair_with_defaults(&room_id).await; + let (session, _) = + account.create_group_session_pair_with_defaults(&room_id).await; let user_id = user_id!("@example:localhost"); let request = ToDeviceRequest::new( @@ -338,7 +357,6 @@ macro_rules! cryptostore_integration_tests { session_info(&sessions[9]), ]).await.expect("Failed to mark sessions as backed up"); - // And ask which still need backing up let to_back_up = store.inbound_group_sessions_for_backup(10).await.unwrap(); let needs_backing_up = |i: usize| to_back_up.iter().any(|s| s.session_id() == sessions[i].session_id()); @@ -909,7 +927,7 @@ macro_rules! cryptostore_integration_tests { #[async_test] async fn backup_keys_saving() { - let (account, store) = get_loaded_store("backup_keys_saving").await; + let (_account, store) = get_loaded_store("backup_keys_saving").await; let restored = store.load_backup_keys().await.unwrap(); assert!(restored.decryption_key.is_none(), "Initially no backup decryption key should be present"); From 7de5d295b67339e4d30daeb59680e1495928bf47 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 20 Mar 2024 13:19:11 +0000 Subject: [PATCH 2/3] crypto: Enable testing module in test mode --- crates/matrix-sdk-crypto/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-crypto/src/lib.rs b/crates/matrix-sdk-crypto/src/lib.rs index 04396da14..ac0468879 100644 --- a/crates/matrix-sdk-crypto/src/lib.rs +++ b/crates/matrix-sdk-crypto/src/lib.rs @@ -33,7 +33,7 @@ pub mod types; mod utilities; mod verification; -#[cfg(feature = "testing")] +#[cfg(any(test, feature = "testing"))] /// Testing facilities and helpers for crypto tests pub mod testing { pub use crate::identities::{ From 3a7b8fc6a5aad9480faae8680661da275718dfb1 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 20 Mar 2024 12:13:47 +0000 Subject: [PATCH 3/3] crypto: Run the crypto integration tests against MemoryStore --- .../src/store/memorystore.rs | 262 ++++++++++++++++++ 1 file changed, 262 insertions(+) diff --git a/crates/matrix-sdk-crypto/src/store/memorystore.rs b/crates/matrix-sdk-crypto/src/store/memorystore.rs index 43f97fc7d..a45d6e99e 100644 --- a/crates/matrix-sdk-crypto/src/store/memorystore.rs +++ b/crates/matrix-sdk-crypto/src/store/memorystore.rs @@ -634,3 +634,265 @@ mod tests { assert!(store.is_message_known(&hash).await.unwrap()); } } + +#[cfg(test)] +mod integration_tests { + use std::{ + collections::HashMap, + sync::{Arc, Mutex, OnceLock}, + }; + + use async_trait::async_trait; + use ruma::{ + events::secret::request::SecretName, DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId, + }; + + use super::MemoryStore; + use crate::{ + cryptostore_integration_tests, cryptostore_integration_tests_time, + olm::{ + InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity, + StaticAccountData, + }, + store::{BackupKeys, Changes, CryptoStore, PendingChanges, RoomKeyCounts, RoomSettings}, + types::events::room_key_withheld::RoomKeyWithheldEvent, + Account, GossipRequest, GossippedSecret, ReadOnlyDevice, ReadOnlyUserIdentities, + SecretInfo, Session, TrackedUser, + }; + + /// Holds on to a MemoryStore during a test, and moves it back into STORES + /// when this is dropped + #[derive(Clone, Debug)] + struct PersistentMemoryStore(Arc); + + impl PersistentMemoryStore { + fn new() -> Self { + Self(Arc::new(MemoryStore::new())) + } + + fn get_static_account(&self) -> Option { + self.0.get_static_account() + } + } + + impl MemoryStore { + fn get_static_account(&self) -> Option { + self.account.read().unwrap().as_ref().map(|acc| acc.static_data().clone()) + } + } + + /// Return a clone of the store for the test with the supplied name. Note: + /// dropping this store won't destroy its data, since + /// [PersistentMemoryStore] is a reference-counted smart pointer + /// to an underlying [MemoryStore]. + async fn get_store(name: &str, _passphrase: Option<&str>) -> PersistentMemoryStore { + // Holds on to one [PersistentMemoryStore] per test, so even if the test drops + // the store, we keep its data alive. This simulates the behaviour of + // the other stores, which keep their data in a real DB, allowing us to + // test MemoryStore using the same code. + static STORES: OnceLock>> = OnceLock::new(); + let stores = STORES.get_or_init(|| Mutex::new(HashMap::new())); + + stores + .lock() + .unwrap() + .entry(name.to_owned()) + .or_insert_with(PersistentMemoryStore::new) + .clone() + } + + /// Forwards all methods to the underlying [MemoryStore]. + #[async_trait] + impl CryptoStore for PersistentMemoryStore { + type Error = ::Error; + + async fn load_account(&self) -> Result, Self::Error> { + self.0.load_account().await + } + + async fn load_identity(&self) -> Result, Self::Error> { + self.0.load_identity().await + } + + async fn save_changes(&self, changes: Changes) -> Result<(), Self::Error> { + self.0.save_changes(changes).await + } + + async fn save_pending_changes(&self, changes: PendingChanges) -> Result<(), Self::Error> { + self.0.save_pending_changes(changes).await + } + + async fn get_sessions( + &self, + sender_key: &str, + ) -> Result>>>, Self::Error> { + self.0.get_sessions(sender_key).await + } + + async fn get_inbound_group_session( + &self, + room_id: &RoomId, + session_id: &str, + ) -> Result, Self::Error> { + self.0.get_inbound_group_session(room_id, session_id).await + } + + async fn get_withheld_info( + &self, + room_id: &RoomId, + session_id: &str, + ) -> Result, Self::Error> { + self.0.get_withheld_info(room_id, session_id).await + } + + async fn get_inbound_group_sessions( + &self, + ) -> Result, Self::Error> { + self.0.get_inbound_group_sessions().await + } + + async fn inbound_group_session_counts(&self) -> Result { + self.0.inbound_group_session_counts().await + } + + async fn inbound_group_sessions_for_backup( + &self, + limit: usize, + ) -> Result, Self::Error> { + self.0.inbound_group_sessions_for_backup(limit).await + } + + async fn mark_inbound_group_sessions_as_backed_up( + &self, + room_and_session_ids: &[(&RoomId, &str)], + ) -> Result<(), Self::Error> { + self.0.mark_inbound_group_sessions_as_backed_up(room_and_session_ids).await + } + + async fn reset_backup_state(&self) -> Result<(), Self::Error> { + self.0.reset_backup_state().await + } + + async fn load_backup_keys(&self) -> Result { + self.0.load_backup_keys().await + } + + async fn get_outbound_group_session( + &self, + room_id: &RoomId, + ) -> Result, Self::Error> { + self.0.get_outbound_group_session(room_id).await + } + + async fn load_tracked_users(&self) -> Result, Self::Error> { + self.0.load_tracked_users().await + } + + async fn save_tracked_users(&self, users: &[(&UserId, bool)]) -> Result<(), Self::Error> { + self.0.save_tracked_users(users).await + } + + async fn get_device( + &self, + user_id: &UserId, + device_id: &DeviceId, + ) -> Result, Self::Error> { + self.0.get_device(user_id, device_id).await + } + + async fn get_user_devices( + &self, + user_id: &UserId, + ) -> Result, Self::Error> { + self.0.get_user_devices(user_id).await + } + + async fn get_user_identity( + &self, + user_id: &UserId, + ) -> Result, Self::Error> { + self.0.get_user_identity(user_id).await + } + + async fn is_message_known( + &self, + message_hash: &OlmMessageHash, + ) -> Result { + self.0.is_message_known(message_hash).await + } + + async fn get_outgoing_secret_requests( + &self, + request_id: &TransactionId, + ) -> Result, Self::Error> { + self.0.get_outgoing_secret_requests(request_id).await + } + + async fn get_secret_request_by_info( + &self, + secret_info: &SecretInfo, + ) -> Result, Self::Error> { + self.0.get_secret_request_by_info(secret_info).await + } + + async fn get_unsent_secret_requests(&self) -> Result, Self::Error> { + self.0.get_unsent_secret_requests().await + } + + async fn delete_outgoing_secret_requests( + &self, + request_id: &TransactionId, + ) -> Result<(), Self::Error> { + self.0.delete_outgoing_secret_requests(request_id).await + } + + async fn get_secrets_from_inbox( + &self, + secret_name: &SecretName, + ) -> Result, Self::Error> { + self.0.get_secrets_from_inbox(secret_name).await + } + + async fn delete_secrets_from_inbox( + &self, + secret_name: &SecretName, + ) -> Result<(), Self::Error> { + self.0.delete_secrets_from_inbox(secret_name).await + } + + async fn get_room_settings( + &self, + room_id: &RoomId, + ) -> Result, Self::Error> { + self.0.get_room_settings(room_id).await + } + + async fn get_custom_value(&self, key: &str) -> Result>, Self::Error> { + self.0.get_custom_value(key).await + } + + async fn set_custom_value(&self, key: &str, value: Vec) -> Result<(), Self::Error> { + self.0.set_custom_value(key, value).await + } + + async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error> { + self.0.remove_custom_value(key).await + } + + async fn try_take_leased_lock( + &self, + lease_duration_ms: u32, + key: &str, + holder: &str, + ) -> Result { + self.0.try_take_leased_lock(lease_duration_ms, key, holder).await + } + + async fn next_batch_token(&self) -> Result, Self::Error> { + self.0.next_batch_token().await + } + } + + cryptostore_integration_tests!(); + cryptostore_integration_tests_time!(); +}