From 89ff804333b8d051c15abd9012ac2d23c0148100 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 10 Mar 2022 12:35:10 +0100 Subject: [PATCH 01/36] Remove extraneous newline --- crates/matrix-sdk-sled/src/state_store.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index 14d60ab1b..c4fe817a4 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -1449,7 +1449,6 @@ struct TimelineMetadata { #[cfg(test)] mod test { - use matrix_sdk_base::statestore_integration_tests; use super::{SledStore, StateStore, StoreResult}; From 54a253c0bf5913ad0aa1d70b45e60911657778dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 9 Mar 2022 09:06:05 +0100 Subject: [PATCH 02/36] fix(crypto): Make our backup feature compile again --- .../src/backups/keys/backup.rs | 9 +- .../matrix-sdk-crypto/src/backups/keys/mod.rs | 2 +- .../src/backups/keys/recovery.rs | 103 +---------------- crates/matrix-sdk-crypto/src/backups/mod.rs | 15 ++- crates/matrix-sdk-crypto/src/machine.rs | 2 +- crates/matrix-sdk-crypto/src/olm/account.rs | 1 + .../src/olm/group_sessions/inbound.rs | 4 +- crates/matrix-sdk-crypto/src/store/mod.rs | 106 +++++++++++++++++- crates/matrix-sdk-sled/src/cryptostore.rs | 18 +-- 9 files changed, 128 insertions(+), 132 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/backups/keys/backup.rs b/crates/matrix-sdk-crypto/src/backups/keys/backup.rs index 4d47cbd0e..fae3abee7 100644 --- a/crates/matrix-sdk-crypto/src/backups/keys/backup.rs +++ b/crates/matrix-sdk-crypto/src/backups/keys/backup.rs @@ -20,6 +20,7 @@ use std::{ use olm_rs::pk::OlmPkEncryption; use ruma::{ api::client::backup::{KeyBackupData, KeyBackupDataInit, SessionDataInit}, + serde::Base64, DeviceKeyId, UserId, }; use zeroize::Zeroizing; @@ -127,9 +128,11 @@ impl MegolmV1BackupKey { let message = pk.encrypt(&key); let session_data = SessionDataInit { - ephemeral: message.ephemeral_key, - ciphertext: message.ciphertext, - mac: message.mac, + ephemeral: Base64::parse(message.ephemeral_key) + .expect("Can't decode the base64 encoded ephemeral backup key"), + ciphertext: Base64::parse(message.ciphertext) + .expect("Can't decode a base64 encoded libolm ciphertext"), + mac: Base64::parse(message.mac).expect("Can't decode a base64 encoded MAC"), } .into(); diff --git a/crates/matrix-sdk-crypto/src/backups/keys/mod.rs b/crates/matrix-sdk-crypto/src/backups/keys/mod.rs index 145d0c011..e9f4e8d22 100644 --- a/crates/matrix-sdk-crypto/src/backups/keys/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/keys/mod.rs @@ -51,4 +51,4 @@ mod backup; mod recovery; pub use backup::MegolmV1BackupKey; -pub use recovery::{DecodeError, PickledRecoveryKey, RecoveryKey}; +pub use recovery::DecodeError; diff --git a/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs b/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs index 88ca3dfbb..10abfc74f 100644 --- a/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs +++ b/crates/matrix-sdk-crypto/src/backups/keys/recovery.rs @@ -17,25 +17,17 @@ use std::{ io::{Cursor, Read}, }; -use aes::cipher::generic_array::GenericArray; -use aes_gcm::{ - aead::{Aead, NewAead}, - Aes256Gcm, -}; use bs58; use olm_rs::{ errors::OlmPkDecryptionError, pk::{OlmPkDecryption, PkMessage}, }; use rand::{thread_rng, Error as RandomError, Fill}; -use serde::{Deserialize, Serialize}; use thiserror::Error; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; use super::MegolmV1BackupKey; -use crate::utilities::{decode_url_safe, encode, encode_url_safe}; - -const NONCE_SIZE: usize = 12; +use crate::{store::RecoveryKey, utilities::encode}; /// Error type for the decoding of a RecoveryKey. #[derive(Debug, Error)] @@ -64,47 +56,12 @@ pub enum DecodeError { pub enum UnpicklingError { #[error(transparent)] Json(#[from] serde_json::Error), - #[error("Couldn't decrypt the pickle: {0}")] - Decryption(String), + // #[error("Couldn't decrypt the pickle: {0}")] + // Decryption(String), #[error(transparent)] Decode(#[from] DecodeError), } -/// The private part of a backup key. -#[derive(Zeroize)] -pub struct RecoveryKey { - inner: [u8; RecoveryKey::KEY_SIZE], -} - -impl Drop for RecoveryKey { - fn drop(&mut self) { - self.inner.zeroize() - } -} - -impl std::fmt::Debug for RecoveryKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RecoveryKey").finish() - } -} - -/// The pickled version of a recovery key. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PickledRecoveryKey(String); - -impl AsRef for PickledRecoveryKey { - fn as_ref(&self) -> &str { - &self.0 - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct InnerPickle { - version: u8, - nonce: String, - ciphertext: String, -} - impl TryFrom for RecoveryKey { type Error = DecodeError; @@ -132,7 +89,6 @@ impl std::fmt::Display for RecoveryKey { } impl RecoveryKey { - const KEY_SIZE: usize = 32; const PREFIX: [u8; 2] = [0x8b, 0x01]; const PREFIX_PARITY: u8 = Self::PREFIX[0] ^ Self::PREFIX[1]; const DISPLAY_CHUNK_SIZE: usize = 4; @@ -235,57 +191,6 @@ impl RecoveryKey { public_key } - /// Export this [`RecoveryKey`] as an encrypted pickle that can be safely - /// stored. - pub fn pickle(&self, pickle_key: &[u8]) -> PickledRecoveryKey { - let key = GenericArray::from_slice(pickle_key); - let cipher = Aes256Gcm::new(key); - - let mut nonce = vec![0u8; NONCE_SIZE]; - let mut rng = thread_rng(); - - nonce.try_fill(&mut rng).expect("Can't generate random nocne to pickle the recovery key"); - let nonce = GenericArray::from_slice(nonce.as_slice()); - - let ciphertext = - cipher.encrypt(nonce, self.inner.as_ref()).expect("Can't encrypt recovery key"); - - let ciphertext = encode_url_safe(ciphertext); - - let pickle = - InnerPickle { version: 1, nonce: encode_url_safe(nonce.as_slice()), ciphertext }; - - PickledRecoveryKey(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) - } - - /// Try to import a `RecoveryKey` from a previously exported pickle. - pub fn from_pickle( - pickle: PickledRecoveryKey, - pickle_key: &[u8], - ) -> Result { - let pickled: InnerPickle = serde_json::from_str(pickle.as_ref())?; - - let key = GenericArray::from_slice(pickle_key); - let cipher = Aes256Gcm::new(key); - - let nonce = decode_url_safe(pickled.nonce).map_err(DecodeError::from)?; - let nonce = GenericArray::from_slice(&nonce); - let ciphertext = &decode_url_safe(pickled.ciphertext).map_err(DecodeError::from)?; - - let decrypted = cipher - .decrypt(nonce, ciphertext.as_slice()) - .map_err(|e| UnpicklingError::Decryption(e.to_string()))?; - - if decrypted.len() != Self::KEY_SIZE { - Err(DecodeError::Length(decrypted.len(), Self::KEY_SIZE).into()) - } else { - let mut key = [0u8; Self::KEY_SIZE]; - key.copy_from_slice(&decrypted); - - Ok(Self { inner: key }) - } - } - /// Try to decrypt the given ciphertext using this `RecoveryKey`. /// /// This will use the [`m.megolm_backup.v1.curve25519-aes-sha2`] algorithm diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index c8d5e18d2..c679df428 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -39,13 +39,13 @@ use tracing::{debug, info, instrument, trace, warn}; use crate::{ olm::{Account, InboundGroupSession}, - store::{BackupKeys, Changes, RoomKeyCounts, Store}, + store::{BackupKeys, Changes, RecoveryKey, RoomKeyCounts, Store}, CryptoStoreError, KeysBackupRequest, OutgoingRequest, }; mod keys; -pub use keys::{DecodeError, MegolmV1BackupKey, PickledRecoveryKey, RecoveryKey}; +pub use keys::{DecodeError, MegolmV1BackupKey}; pub use olm_rs::errors::OlmPkDecryptionError; /// A state machine that handles backing up room keys. @@ -64,7 +64,7 @@ pub struct BackupMachine { #[derive(Debug, Clone)] struct PendingBackup { - request_id: &TransactionId, + request_id: Box, request: KeysBackupRequest, sessions: BTreeMap, BTreeMap>>, } @@ -388,8 +388,7 @@ mod test { use matrix_sdk_test::async_test; use ruma::{device_id, room_id, user_id, DeviceId, RoomId, UserId}; - use super::RecoveryKey; - use crate::{OlmError, OlmMachine}; + use crate::{store::RecoveryKey, OlmError, OlmMachine}; fn alice_id() -> &'static UserId { user_id!("@alice:example.org") @@ -430,12 +429,12 @@ mod test { let request = backup_machine.backup().await?.expect("Created a backup request successfully"); assert_eq!( - Some(request.request_id), - backup_machine.backup().await?.map(|r| r.request_id), + Some(&*request.request_id), + backup_machine.backup().await?.as_ref().map(|r| &*r.request_id), "Calling backup again without uploading creates the same backup request" ); - backup_machine.mark_request_as_sent(request.request_id).await?; + backup_machine.mark_request_as_sent(&request.request_id).await?; let counts = backup_machine.store.inbound_group_session_counts().await?; assert_eq!(counts.total, 2); diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 2411b3bb3..613b92773 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -376,7 +376,7 @@ impl OlmMachine { } IncomingResponse::KeysBackup(_) => { #[cfg(feature = "backups_v1")] - self.backup_machine.mark_request_as_sent(*request_id).await?; + self.backup_machine.mark_request_as_sent(request_id).await?; } }; diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index fd003b1f1..12ab17640 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -696,6 +696,7 @@ impl ReadOnlyAccount { self.inner.lock().await.sign(string) } + /// Check that the given json value is signed by this account. #[cfg(feature = "backups_v1")] pub fn is_signed(&self, json: &mut Value) -> Result<(), SignatureError> { let signing_key = self.identity_keys.ed25519(); diff --git a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs index 715b3ad07..7c34fb9c3 100644 --- a/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs +++ b/crates/matrix-sdk-crypto/src/olm/group_sessions/inbound.rs @@ -238,8 +238,6 @@ impl InboundGroupSession { self.backed_up.store(false, SeqCst) } - #[cfg(any(test, feature = "testing"))] - #[allow(dead_code)] /// For testing, allow to manually mark this GroupSession to have been /// backed up pub fn mark_as_backed_up(&self) { @@ -342,6 +340,8 @@ impl InboundGroupSession { self.inner.lock().await.decrypt(message) } + /// Export the inbound group session into a format that can be uploaded to + /// the server as a backup. #[cfg(feature = "backups_v1")] pub async fn to_backup(&self) -> BackedUpRoomKey { self.export().await.into() diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 4f8c9a122..4a4c39c8e 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -65,6 +65,7 @@ use ruma::{ events::secret::request::SecretName, identifiers::Error as IdentifierValidationError, DeviceId, DeviceKeyAlgorithm, RoomId, TransactionId, UserId, }; +use serde::{Deserialize, Serialize}; use serde_json::Error as SerdeError; use thiserror::Error; use tracing::{info, warn}; @@ -108,10 +109,8 @@ pub struct Store { pub struct Changes { pub account: Option, pub private_identity: Option, - #[cfg(feature = "backups_v1")] pub backup_version: Option, - #[cfg(feature = "backups_v1")] - pub recovery_key: Option, + pub recovery_key: Option, pub sessions: Vec, pub message_hashes: Vec, pub inbound_group_sessions: Vec, @@ -157,6 +156,103 @@ pub struct DeviceChanges { pub deleted: Vec, } +/// The private part of a backup key. +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct RecoveryKey { + pub(crate) inner: [u8; RecoveryKey::KEY_SIZE], +} + +/// The pickled version of a recovery key. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PickledRecoveryKey(String); + +impl AsRef for PickledRecoveryKey { + fn as_ref(&self) -> &str { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct InnerPickle { + version: u8, + nonce: String, + ciphertext: String, +} + +impl RecoveryKey { + /// The number of bytes the recovery key will hold. + pub const KEY_SIZE: usize = 32; + const NONCE_SIZE: usize = 12; + + /// Export this [`RecoveryKey`] as an encrypted pickle that can be safely + /// stored. + pub fn pickle(&self, pickle_key: &[u8]) -> PickledRecoveryKey { + use aes::cipher::generic_array::GenericArray; + use aes_gcm::aead::{Aead, NewAead}; + use rand::Fill; + + let key = GenericArray::from_slice(pickle_key); + let cipher = aes_gcm::Aes256Gcm::new(key); + + let mut nonce = vec![0u8; Self::NONCE_SIZE]; + let mut rng = rand::thread_rng(); + + nonce.try_fill(&mut rng).expect("Can't generate random nocne to pickle the recovery key"); + let nonce = GenericArray::from_slice(nonce.as_slice()); + + let ciphertext = + cipher.encrypt(nonce, self.inner.as_ref()).expect("Can't encrypt recovery key"); + + let ciphertext = crate::utilities::encode_url_safe(ciphertext); + + let pickle = InnerPickle { + version: 1, + nonce: crate::utilities::encode_url_safe(nonce.as_slice()), + ciphertext, + }; + + PickledRecoveryKey(serde_json::to_string(&pickle).expect("Can't encode pickled signing")) + } + + /// Try to import a `RecoveryKey` from a previously exported pickle. + pub fn from_pickle( + pickle: PickledRecoveryKey, + pickle_key: &[u8], + ) -> Result { + use aes::cipher::generic_array::GenericArray; + use aes_gcm::aead::{Aead, NewAead}; + + let pickled: InnerPickle = serde_json::from_str(pickle.as_ref())?; + + let key = GenericArray::from_slice(pickle_key); + let cipher = aes_gcm::Aes256Gcm::new(key); + + let nonce = crate::utilities::decode_url_safe(pickled.nonce).unwrap(); + let nonce = GenericArray::from_slice(&nonce); + let ciphertext = &crate::utilities::decode_url_safe(pickled.ciphertext).unwrap(); + + let decrypted = cipher + .decrypt(nonce, ciphertext.as_slice()) + .map_err(|_| CryptoStoreError::UnpicklingError)?; + + if decrypted.len() != Self::KEY_SIZE { + Err(CryptoStoreError::UnpicklingError) + } else { + let mut key = [0u8; Self::KEY_SIZE]; + key.copy_from_slice(&decrypted); + + Ok(Self { inner: key }) + } + } +} + +impl Debug for RecoveryKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RecoveryKey").finish() + } +} + impl DeviceChanges { /// Merge the given `DeviceChanges` into this instance of `DeviceChanges`. pub fn extend(&mut self, other: DeviceChanges) { @@ -183,10 +279,8 @@ pub struct RoomKeyCounts { #[derive(Default, Debug)] pub struct BackupKeys { /// The recovery key, the one used to decrypt backed up room keys. - #[cfg(feature = "backups_v1")] - pub recovery_key: Option, + pub recovery_key: Option, /// The version that we are using for backups. - #[cfg(feature = "backups_v1")] pub backup_version: Option, } diff --git a/crates/matrix-sdk-sled/src/cryptostore.rs b/crates/matrix-sdk-sled/src/cryptostore.rs index c3270ae76..d35d1bd74 100644 --- a/crates/matrix-sdk-sled/src/cryptostore.rs +++ b/crates/matrix-sdk-sled/src/cryptostore.rs @@ -37,7 +37,7 @@ use matrix_sdk_crypto::{ }, store::{ caches::SessionStore, BackupKeys, Changes, CryptoStore, CryptoStoreError, IdentityKeys, - PickleKey, PicklingMode, Result, RoomKeyCounts, + PickleKey, PicklingMode, RecoveryKey, Result, RoomKeyCounts, }, GossipRequest, LocalTrust, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo, }; @@ -518,7 +518,6 @@ impl SledStore { None }; - #[cfg(feature = "backups_v1")] let recovery_key_pickle = changes.recovery_key.map(|r| r.pickle(self.get_pickle_key())); let device_changes = changes.devices; @@ -559,7 +558,6 @@ impl SledStore { let identity_changes = changes.identities; let olm_hashes = changes.message_hashes; let key_requests = changes.key_requests; - #[cfg(feature = "backups_v1")] let backup_version = changes.backup_version; let ret: Result<(), TransactionError> = ( @@ -603,7 +601,6 @@ impl SledStore { )?; } - #[cfg(feature = "backups_v1")] if let Some(r) = &recovery_key_pickle { account.insert( "recovery_key_v1".encode(), @@ -611,7 +608,6 @@ impl SledStore { )?; } - #[cfg(feature = "backups_v1")] if let Some(b) = &backup_version { account.insert( "backup_version_v1".encode(), @@ -1071,21 +1067,22 @@ impl CryptoStore for SledStore { } async fn load_backup_keys(&self) -> Result { - #[cfg(feature = "backups_v1")] let key = { let backup_version = self .account - .get("backup_version_v1".encode())? + .get("backup_version_v1".encode()) + .map_err(|e| CryptoStoreError::Backend(anyhow!(e)))? .map(|v| serde_json::from_slice(&v)) .transpose()?; let recovery_key = { self.account - .get("recovery_key_v1".encode())? + .get("recovery_key_v1".encode()) + .map_err(|e| CryptoStoreError::Backend(anyhow!(e)))? .map(|p| serde_json::from_slice(&p)) .transpose()? .map(|p| { - crate::backups::RecoveryKey::from_pickle(p, self.get_pickle_key()) + RecoveryKey::from_pickle(p, self.get_pickle_key()) .map_err(|_| CryptoStoreError::UnpicklingError) }) .transpose()? @@ -1094,9 +1091,6 @@ impl CryptoStore for SledStore { BackupKeys { backup_version, recovery_key } }; - #[cfg(not(feature = "backups_v1"))] - let key = BackupKeys {}; - Ok(key) } } From 4af4faacef4372503e339441b091d46a79f85201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 9 Mar 2022 12:04:13 +0100 Subject: [PATCH 03/36] feat(xtask): Add a task to check the crypto crate features --- xtask/src/ci.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index ce04b8c2c..824b822e2 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -36,6 +36,8 @@ enum CiCommand { #[clap(subcommand)] cmd: Option, }, + /// Run tests for the different crypto crate features + TestCrypto, } #[derive(Subcommand, PartialEq, Eq, PartialOrd, Ord)] @@ -76,6 +78,7 @@ impl CiArgs { CiCommand::TestFeatures { cmd } => run_feature_tests(cmd), CiCommand::TestAppservice => run_appservice_tests(), CiCommand::Wasm { cmd } => run_wasm_checks(cmd), + CiCommand::TestCrypto => run_crypto_tests(), }, None => { check_style()?; @@ -86,6 +89,7 @@ impl CiArgs { run_feature_tests(None)?; run_appservice_tests()?; run_wasm_checks(None)?; + run_crypto_tests()?; Ok(()) } @@ -159,6 +163,16 @@ fn run_feature_tests(cmd: Option) -> Result<()> { Ok(()) } +fn run_crypto_tests() -> Result<()> { + cmd!( + "rustup run stable cargo clippy -p matrix-sdk-crypto --features=backups_v1 -- -D warnings" + ) + .run()?; + cmd!("rustup run stable cargo test -p matrix-sdk-crypto --features=backups_v1").run()?; + + Ok(()) +} + fn run_appservice_tests() -> Result<()> { cmd!("rustup run stable cargo clippy -p matrix-sdk-appservice -- -D warnings").run()?; cmd!("rustup run stable cargo test -p matrix-sdk-appservice").run()?; From 691ea2d1389a707bd1a4dfe46fa68bac04e33000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 10 Mar 2022 12:53:49 +0100 Subject: [PATCH 04/36] ci(crypto): Test the crypto crate features when we run the CI --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88fd21fd1..07d4c9a80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,6 +117,32 @@ jobs: command: run args: -p xtask -- ci test-features ${{ matrix.name }} + test-crypto-features: + name: linux / crypto-crate features + needs: [style] + runs-on: ubuntu-latest + if: github.event_name == 'push' || !github.event.pull_request.draft + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Load cache + uses: Swatinem/rust-cache@v1 + + - name: Clippy + uses: actions-rs/cargo@v1 + with: + command: run + args: -p xtask -- ci test-crypto + test: name: ${{ matrix.name }} if: github.event_name == 'push' || !github.event.pull_request.draft From 1dec224210fb6d7ed4a5aabb05394aebbbfb2c31 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 10 Mar 2022 12:56:27 +0100 Subject: [PATCH 05/36] Use fine-grained *EventType types --- crates/matrix-sdk-base/src/client.rs | 12 ++--- crates/matrix-sdk-base/src/rooms/normal.rs | 7 +-- .../src/store/integration_tests.rs | 25 +++++----- .../matrix-sdk-base/src/store/memory_store.rs | 19 ++++---- crates/matrix-sdk-base/src/store/mod.rs | 13 ++--- crates/matrix-sdk-crypto/src/requests.rs | 6 +-- .../src/session_manager/group_sessions.rs | 4 +- .../src/verification/event_enums.rs | 48 +++++++++++-------- .../matrix-sdk-indexeddb/src/safe_encode.rs | 6 +-- .../matrix-sdk-indexeddb/src/state_store.rs | 19 ++++---- crates/matrix-sdk-sled/bin/state_inspector.rs | 11 +++-- crates/matrix-sdk-sled/src/state_store.rs | 33 +++++++++---- crates/matrix-sdk/src/client.rs | 6 +-- crates/matrix-sdk/src/encryption/mod.rs | 4 +- crates/matrix-sdk/src/room/common.rs | 6 +-- 15 files changed, 125 insertions(+), 94 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index bdb06b7bd..d4dd804d5 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -54,7 +54,7 @@ use ruma::{ events::{ room::member::MembershipState, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent, - EventContent, EventType, + EventContent, GlobalAccountDataEventType, StateEventType, }, push::{Action, PushConditionRoomCtx, Ruleset}, serde::Raw, @@ -1197,13 +1197,13 @@ impl BaseClient { pub async fn get_push_rules(&self, changes: &StateChanges) -> Result { if let Some(AnyGlobalAccountDataEvent::PushRules(event)) = changes .account_data - .get(EventType::PushRules.as_str()) + .get(GlobalAccountDataEventType::PushRules.as_str()) .and_then(|e| e.deserialize().ok()) { Ok(event.content.global) } else if let Some(AnyGlobalAccountDataEvent::PushRules(event)) = self .store - .get_account_data_event(EventType::PushRules) + .get_account_data_event(GlobalAccountDataEventType::PushRules) .await? .and_then(|e| e.deserialize().ok()) { @@ -1246,14 +1246,14 @@ impl BaseClient { let room_power_levels = if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes .state .get(room_id) - .and_then(|types| types.get(EventType::RoomPowerLevels.as_str())) + .and_then(|types| types.get(StateEventType::RoomPowerLevels.as_str())) .and_then(|events| events.get("")) .and_then(|e| e.deserialize().ok()) { event.content } else if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = self .store - .get_state_event(room_id, EventType::RoomPowerLevels, "") + .get_state_event(room_id, StateEventType::RoomPowerLevels, "") .await? .and_then(|e| e.deserialize().ok()) { @@ -1296,7 +1296,7 @@ impl BaseClient { if let Some(AnySyncStateEvent::RoomPowerLevels(event)) = changes .state .get(&**room_id) - .and_then(|types| types.get(EventType::RoomPowerLevels.as_str())) + .and_then(|types| types.get(StateEventType::RoomPowerLevels.as_str())) .and_then(|events| events.get("")) .and_then(|e| e.deserialize().ok()) { diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index c2085a920..6db504333 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -32,7 +32,8 @@ use ruma::{ tombstone::RoomTombstoneEventContent, }, tag::Tags, - AnyRoomAccountDataEvent, AnyStateEventContent, AnySyncStateEvent, EventType, + AnyRoomAccountDataEvent, AnyStateEventContent, AnySyncStateEvent, RoomAccountDataEventType, + StateEventType, }, receipt::ReceiptType, EventId, MxcUri, RoomAliasId, RoomId, UserId, @@ -415,7 +416,7 @@ impl Room { let power = self.store - .get_state_event(self.room_id(), EventType::RoomPowerLevels, "") + .get_state_event(self.room_id(), StateEventType::RoomPowerLevels, "") .await? .and_then(|e| e.deserialize().ok()) .and_then(|e| { @@ -451,7 +452,7 @@ impl Room { pub async fn tags(&self) -> StoreResult> { if let Some(AnyRoomAccountDataEvent::Tag(event)) = self .store - .get_room_account_data_event(self.room_id(), EventType::Tag) + .get_room_account_data_event(self.room_id(), RoomAccountDataEventType::Tag) .await? .and_then(|r| r.deserialize().ok()) { diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index f6bd68d54..fdb82073e 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -51,9 +51,10 @@ macro_rules! statestore_integration_tests { member::{MembershipState, RoomMemberEventContent}, power_levels::RoomPowerLevelsEventContent, }, - AnyEphemeralRoomEventContent, AnySyncEphemeralRoomEvent, AnyStrippedStateEvent, - AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, - AnySyncStateEvent, EventType, Unsigned, + AnyEphemeralRoomEventContent, AnySyncEphemeralRoomEvent, + AnyStrippedStateEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, + AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, + StateEventType, Unsigned, }, mxc_uri, receipt::ReceiptType, @@ -279,10 +280,10 @@ macro_rules! statestore_integration_tests { assert!(store.get_presence_event(user_id).await?.is_some()); assert_eq!(store.get_room_infos().await?.len(), 1); assert_eq!(store.get_stripped_room_infos().await?.len(), 1); - assert!(store.get_account_data_event(EventType::PushRules).await?.is_some()); + assert!(store.get_account_data_event(GlobalAccountDataEventType::PushRules).await?.is_some()); - assert!(store.get_state_event(room_id, EventType::RoomName, "").await?.is_some()); - assert_eq!(store.get_state_events(room_id, EventType::RoomTopic).await?.len(), 1); + assert!(store.get_state_event(room_id, StateEventType::RoomName, "").await?.is_some()); + assert_eq!(store.get_state_events(room_id, StateEventType::RoomTopic).await?.len(), 1); assert!(store.get_profile(room_id, user_id).await?.is_some()); assert!(store.get_member_event(room_id, user_id).await?.is_some()); assert_eq!(store.get_user_ids(room_id).await?.len(), 2); @@ -290,7 +291,7 @@ macro_rules! statestore_integration_tests { assert_eq!(store.get_joined_user_ids(room_id).await?.len(), 1); assert_eq!(store.get_users_with_display_name(room_id, "example").await?.len(), 2); assert!(store - .get_room_account_data_event(room_id, EventType::Tag) + .get_room_account_data_event(room_id, RoomAccountDataEventType::Tag) .await? .is_some()); assert!(store @@ -337,7 +338,7 @@ macro_rules! statestore_integration_tests { let event = raw_event.deserialize().unwrap(); assert!(store - .get_state_event(room_id, EventType::RoomPowerLevels, "") + .get_state_event(room_id, StateEventType::RoomPowerLevels, "") .await .unwrap() .is_none()); @@ -346,7 +347,7 @@ macro_rules! statestore_integration_tests { store.save_changes(&changes).await.unwrap(); assert!(store - .get_state_event(room_id, EventType::RoomPowerLevels, "") + .get_state_event(room_id, StateEventType::RoomPowerLevels, "") .await .unwrap() .is_some()); @@ -527,8 +528,8 @@ macro_rules! statestore_integration_tests { assert_eq!(store.get_room_infos().await?.len(), 0); assert_eq!(store.get_stripped_room_infos().await?.len(), 1); - assert!(store.get_state_event(room_id, EventType::RoomName, "").await?.is_none()); - assert_eq!(store.get_state_events(room_id, EventType::RoomTopic).await?.len(), 0); + assert!(store.get_state_event(room_id, StateEventType::RoomName, "").await?.is_none()); + assert_eq!(store.get_state_events(room_id, StateEventType::RoomTopic).await?.len(), 0); assert!(store.get_profile(room_id, user_id).await?.is_none()); assert!(store.get_member_event(room_id, user_id).await?.is_none()); assert_eq!(store.get_user_ids(room_id).await?.len(), 0); @@ -536,7 +537,7 @@ macro_rules! statestore_integration_tests { assert_eq!(store.get_joined_user_ids(room_id).await?.len(), 0); assert_eq!(store.get_users_with_display_name(room_id, "example").await?.len(), 0); assert!(store - .get_room_account_data_event(room_id, EventType::Tag) + .get_room_account_data_event(room_id, RoomAccountDataEventType::Tag) .await? .is_none()); assert!(store diff --git a/crates/matrix-sdk-base/src/store/memory_store.rs b/crates/matrix-sdk-base/src/store/memory_store.rs index 89093a964..dab3e4918 100644 --- a/crates/matrix-sdk-base/src/store/memory_store.rs +++ b/crates/matrix-sdk-base/src/store/memory_store.rs @@ -28,7 +28,8 @@ use ruma::{ receipt::Receipt, room::member::{MembershipState, RoomMemberEventContent}, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, - AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, EventType, + AnySyncMessageEvent, AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType, + RoomAccountDataEventType, StateEventType, }, receipt::ReceiptType, serde::Raw, @@ -408,7 +409,7 @@ impl MemoryStore { async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>> { Ok(self.room_state.get(room_id).and_then(|e| { @@ -419,7 +420,7 @@ impl MemoryStore { async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> Result>> { Ok(self .room_state @@ -477,7 +478,7 @@ impl MemoryStore { async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> Result>> { Ok(self.account_data.get(event_type.as_ref()).map(|e| e.clone())) } @@ -485,7 +486,7 @@ impl MemoryStore { async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> Result>> { Ok(self .room_account_data @@ -631,7 +632,7 @@ impl StateStore for MemoryStore { async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>> { self.get_state_event(room_id, event_type, state_key).await @@ -640,7 +641,7 @@ impl StateStore for MemoryStore { async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> Result>> { self.get_state_events(room_id, event_type).await } @@ -695,7 +696,7 @@ impl StateStore for MemoryStore { async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> Result>> { self.get_account_data_event(event_type).await } @@ -703,7 +704,7 @@ impl StateStore for MemoryStore { async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> Result>> { self.get_room_account_data_event(room_id, event_type).await } diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 426a448de..5185bf9e3 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -40,7 +40,8 @@ use ruma::{ receipt::{Receipt, ReceiptEventContent}, room::member::RoomMemberEventContent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent, - AnySyncStateEvent, EventContent, EventType, + AnySyncStateEvent, EventContent, GlobalAccountDataEventType, RoomAccountDataEventType, + StateEventType, }, receipt::ReceiptType, serde::Raw, @@ -145,11 +146,11 @@ pub trait StateStore: AsyncTraitDeps { async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>>; - /// Get a list of state events for a given room and `EventType`. + /// Get a list of state events for a given room and `StateEventType`. /// /// # Arguments /// @@ -159,7 +160,7 @@ pub trait StateStore: AsyncTraitDeps { async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> Result>>; /// Get the current profile for the given user in the given room. @@ -226,7 +227,7 @@ pub trait StateStore: AsyncTraitDeps { /// * `event_type` - The event type of the account data event. async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> Result>>; /// Get an event out of the room account data store. @@ -241,7 +242,7 @@ pub trait StateStore: AsyncTraitDeps { async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> Result>>; /// Get an event out of the user room receipt store. diff --git a/crates/matrix-sdk-crypto/src/requests.rs b/crates/matrix-sdk-crypto/src/requests.rs index 3714de645..235ede122 100644 --- a/crates/matrix-sdk-crypto/src/requests.rs +++ b/crates/matrix-sdk-crypto/src/requests.rs @@ -30,7 +30,7 @@ use ruma::{ to_device::send_event_to_device::v3::Response as ToDeviceResponse, }, encryption::CrossSigningKey, - events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, EventType}, + events::{AnyMessageEventContent, AnyToDeviceEventContent, EventContent, ToDeviceEventType}, serde::Raw, to_device::DeviceIdOrAllDevices, DeviceId, RoomId, TransactionId, UserId, @@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ToDeviceRequest { /// Type of event being sent to each device. - pub event_type: EventType, + pub event_type: ToDeviceEventType, /// A request identifier unique to the access token used to send the /// request. @@ -108,7 +108,7 @@ impl ToDeviceRequest { content: AnyToDeviceEventContent, txn_id: Box, ) -> Self { - let event_type = EventType::from(content.event_type()); + let event_type = ToDeviceEventType::from(content.event_type()); let raw_content = Raw::new(&content).expect("Failed to serialize to-device event"); let user_messages = iter::once((recipient_device.into(), raw_content)).collect(); diff --git a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs index 005878876..7353be8c8 100644 --- a/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs +++ b/crates/matrix-sdk-crypto/src/session_manager/group_sessions.rs @@ -24,7 +24,7 @@ use matrix_sdk_common::executor::spawn; use ruma::{ events::{ room::{encrypted::RoomEncryptedEventContent, history_visibility::HistoryVisibility}, - AnyToDeviceEventContent, EventType, + AnyToDeviceEventContent, ToDeviceEventType, }, serde::Raw, to_device::DeviceIdOrAllDevices, @@ -300,7 +300,7 @@ impl GroupSessionManager { let txn_id = TransactionId::new(); let request = ToDeviceRequest { - event_type: EventType::RoomEncrypted, + event_type: ToDeviceEventType::RoomEncrypted, txn_id: txn_id.clone(), messages, }; diff --git a/crates/matrix-sdk-crypto/src/verification/event_enums.rs b/crates/matrix-sdk-crypto/src/verification/event_enums.rs index e1385ec71..7b3a36ee5 100644 --- a/crates/matrix-sdk-crypto/src/verification/event_enums.rs +++ b/crates/matrix-sdk-crypto/src/verification/event_enums.rs @@ -727,7 +727,7 @@ impl TryFrom for OutgoingContent { type Error = String; fn try_from(request: ToDeviceRequest) -> Result { - use ruma::events::EventType; + use ruma::events::ToDeviceEventType; use serde_json::Value; let json: Value = serde_json::from_str( @@ -742,30 +742,40 @@ impl TryFrom for OutgoingContent { .map_err(|e| e.to_string())?; let content = match request.event_type { - EventType::KeyVerificationStart => AnyToDeviceEventContent::KeyVerificationStart( + ToDeviceEventType::KeyVerificationStart => { + AnyToDeviceEventContent::KeyVerificationStart( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ) + } + ToDeviceEventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey( serde_json::from_value(json).map_err(|e| e.to_string())?, ), - EventType::KeyVerificationKey => AnyToDeviceEventContent::KeyVerificationKey( + ToDeviceEventType::KeyVerificationAccept => { + AnyToDeviceEventContent::KeyVerificationAccept( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ) + } + ToDeviceEventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac( serde_json::from_value(json).map_err(|e| e.to_string())?, ), - EventType::KeyVerificationAccept => AnyToDeviceEventContent::KeyVerificationAccept( - serde_json::from_value(json).map_err(|e| e.to_string())?, - ), - EventType::KeyVerificationMac => AnyToDeviceEventContent::KeyVerificationMac( - serde_json::from_value(json).map_err(|e| e.to_string())?, - ), - EventType::KeyVerificationCancel => AnyToDeviceEventContent::KeyVerificationCancel( - serde_json::from_value(json).map_err(|e| e.to_string())?, - ), - EventType::KeyVerificationReady => AnyToDeviceEventContent::KeyVerificationReady( - serde_json::from_value(json).map_err(|e| e.to_string())?, - ), - EventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone( - serde_json::from_value(json).map_err(|e| e.to_string())?, - ), - EventType::KeyVerificationRequest => AnyToDeviceEventContent::KeyVerificationRequest( + ToDeviceEventType::KeyVerificationCancel => { + AnyToDeviceEventContent::KeyVerificationCancel( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ) + } + ToDeviceEventType::KeyVerificationReady => { + AnyToDeviceEventContent::KeyVerificationReady( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ) + } + ToDeviceEventType::KeyVerificationDone => AnyToDeviceEventContent::KeyVerificationDone( serde_json::from_value(json).map_err(|e| e.to_string())?, ), + ToDeviceEventType::KeyVerificationRequest => { + AnyToDeviceEventContent::KeyVerificationRequest( + serde_json::from_value(json).map_err(|e| e.to_string())?, + ) + } e => return Err(format!("Unsupported event type {}", e)), }; diff --git a/crates/matrix-sdk-indexeddb/src/safe_encode.rs b/crates/matrix-sdk-indexeddb/src/safe_encode.rs index c2a3f860d..f0802bef1 100644 --- a/crates/matrix-sdk-indexeddb/src/safe_encode.rs +++ b/crates/matrix-sdk-indexeddb/src/safe_encode.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] +use matrix_sdk_base::ruma::events::StateEventType; use matrix_sdk_common::ruma::{ - events::EventType, receipt::ReceiptType, DeviceId, EventId, MxcUri, RoomId, TransactionId, - UserId, + receipt::ReceiptType, DeviceId, EventId, MxcUri, RoomId, TransactionId, UserId, }; use wasm_bindgen::JsValue; use web_sys::IdbKeyRange; @@ -135,7 +135,7 @@ impl SafeEncode for TransactionId { } } -impl SafeEncode for EventType { +impl SafeEncode for StateEventType { fn as_encoded_string(&self) -> String { self.as_str().as_encoded_string() } diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index 9af422d95..8944f291d 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -34,7 +34,8 @@ use matrix_sdk_common::{ receipt::Receipt, room::member::{MembershipState, RoomMemberEventContent}, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncMessageEvent, - AnySyncRoomEvent, AnySyncStateEvent, EventType, + AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType, + RoomAccountDataEventType, StateEventType, }, receipt::ReceiptType, serde::Raw, @@ -711,7 +712,7 @@ impl IndexeddbStore { pub async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>> { self.inner @@ -726,7 +727,7 @@ impl IndexeddbStore { pub async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> Result>> { let range = (room_id, &event_type).encode_to_range().map_err(StoreError::Codec)?; Ok(self @@ -860,7 +861,7 @@ impl IndexeddbStore { pub async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> Result>> { self.inner .transaction_on_one_with_mode(KEYS::ACCOUNT_DATA, IdbTransactionMode::Readonly)? @@ -874,7 +875,7 @@ impl IndexeddbStore { pub async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> Result>> { self.inner .transaction_on_one_with_mode(KEYS::ROOM_ACCOUNT_DATA, IdbTransactionMode::Readonly)? @@ -1122,7 +1123,7 @@ impl StateStore for IndexeddbStore { async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> StoreResult>> { self.get_state_event(room_id, event_type, state_key).await.map_err(|e| e.into()) @@ -1131,7 +1132,7 @@ impl StateStore for IndexeddbStore { async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> StoreResult>> { self.get_state_events(room_id, event_type).await.map_err(|e| e.into()) } @@ -1182,7 +1183,7 @@ impl StateStore for IndexeddbStore { async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> StoreResult>> { self.get_account_data_event(event_type).await.map_err(|e| e.into()) } @@ -1190,7 +1191,7 @@ impl StateStore for IndexeddbStore { async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> StoreResult>> { self.get_room_account_data_event(room_id, event_type).await.map_err(|e| e.into()) } diff --git a/crates/matrix-sdk-sled/bin/state_inspector.rs b/crates/matrix-sdk-sled/bin/state_inspector.rs index 7b5e480b4..c1edeec97 100644 --- a/crates/matrix-sdk-sled/bin/state_inspector.rs +++ b/crates/matrix-sdk-sled/bin/state_inspector.rs @@ -4,7 +4,7 @@ use atty::Stream; use clap::{Arg, ArgMatches, Command as Argparse}; use futures::executor::block_on; use matrix_sdk_base::{RoomInfo, Store}; -use matrix_sdk_common::ruma::{events::EventType, RoomId, UserId}; +use matrix_sdk_common::ruma::{events::StateEventType, RoomId, UserId}; use matrix_sdk_sled::StateStore; use rustyline::{ completion::{Completer, Pair}, @@ -228,7 +228,8 @@ impl Inspector { } Some(("get-state", args)) => { let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap(); - let event_type = EventType::try_from(args.value_of("event-type").unwrap()).unwrap(); + let event_type = + StateEventType::try_from(args.value_of("event-type").unwrap()).unwrap(); self.get_state(room_id, event_type).await; } _ => unreachable!(), @@ -263,7 +264,7 @@ impl Inspector { } } - async fn get_state(&self, room_id: Box, event_type: EventType) { + async fn get_state(&self, room_id: Box, event_type: StateEventType) { self.printer.pretty_print_struct( &self.store.get_state_event(&room_id, event_type, "").await.unwrap(), ); @@ -288,7 +289,9 @@ impl Inspector { RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned()) })) .arg(Arg::new("event-type").required(true).validator(|e| { - EventType::try_from(e).map(|_| ()).map_err(|_| "Invalid event type".to_owned()) + StateEventType::try_from(e) + .map(|_| ()) + .map_err(|_| "Invalid event type".to_owned()) })), ] } diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index c4fe817a4..f03181dfe 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -42,7 +42,8 @@ use matrix_sdk_common::{ receipt::Receipt, room::member::{MembershipState, RoomMemberEventContent}, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncMessageEvent, - AnySyncRoomEvent, AnySyncStateEvent, EventType, + AnySyncRoomEvent, AnySyncStateEvent, GlobalAccountDataEventType, + RoomAccountDataEventType, StateEventType, }, receipt::ReceiptType, serde::Raw, @@ -186,12 +187,24 @@ impl EncodeKey for (&str, &str, &str, &str) { } } -impl EncodeKey for EventType { +impl EncodeKey for StateEventType { fn encode(&self) -> Vec { self.as_str().encode() } } +impl EncodeKey for GlobalAccountDataEventType { + fn encode(&self) -> Vec { + self.as_str().encode() + } +} + +/* impl EncodeKey for RoomAccountDataEventType { + fn encode(&self) -> Vec { + self.as_str().encode() + } +} */ + impl EncodeKey for EventId { fn encode(&self) -> Vec { self.as_str().encode() @@ -649,7 +662,7 @@ impl SledStore { pub async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>> { let db = self.clone(); @@ -663,7 +676,7 @@ impl SledStore { pub async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> Result>> { let db = self.clone(); let key = (room_id.as_str(), event_type.as_str()).encode(); @@ -809,7 +822,7 @@ impl SledStore { pub async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> Result>> { let db = self.clone(); let key = event_type.encode(); @@ -822,7 +835,7 @@ impl SledStore { pub async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> Result>> { let db = self.clone(); let key = (room_id.as_str(), event_type.as_str()).encode(); @@ -1300,7 +1313,7 @@ impl StateStore for SledStore { async fn get_state_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> StoreResult>> { self.get_state_event(room_id, event_type, state_key).await.map_err(Into::into) @@ -1309,7 +1322,7 @@ impl StateStore for SledStore { async fn get_state_events( &self, room_id: &RoomId, - event_type: EventType, + event_type: StateEventType, ) -> StoreResult>> { self.get_state_events(room_id, event_type).await.map_err(Into::into) } @@ -1370,7 +1383,7 @@ impl StateStore for SledStore { async fn get_account_data_event( &self, - event_type: EventType, + event_type: GlobalAccountDataEventType, ) -> StoreResult>> { self.get_account_data_event(event_type).await.map_err(Into::into) } @@ -1378,7 +1391,7 @@ impl StateStore for SledStore { async fn get_room_account_data_event( &self, room_id: &RoomId, - event_type: EventType, + event_type: RoomAccountDataEventType, ) -> StoreResult>> { self.get_room_account_data_event(room_id, event_type).await.map_err(Into::into) } diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index 42450ed08..b9be4d312 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -2362,7 +2362,7 @@ pub(crate) mod test { message::{ImageMessageEventContent, RoomMessageEventContent}, ImageInfo, }, - AnySyncStateEvent, EventType, + AnySyncStateEvent, StateEventType, }, mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId, }; @@ -3844,14 +3844,14 @@ pub(crate) mod test { let room = client.get_joined_room(room_id).unwrap(); - let state_events = room.get_state_events(EventType::RoomEncryption).await.unwrap(); + let state_events = room.get_state_events(StateEventType::RoomEncryption).await.unwrap(); assert_eq!(state_events.len(), 1); let state_events = room.get_state_events("m.custom.note".into()).await.unwrap(); assert_eq!(state_events.len(), 2); let encryption_event = room - .get_state_event(EventType::RoomEncryption, "") + .get_state_event(StateEventType::RoomEncryption, "") .await .unwrap() .unwrap() diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 08a926767..58f7a54be 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -48,7 +48,7 @@ use ruma::{ uiaa::AuthData, }, assign, - events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent, EventType}, + events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent, GlobalAccountDataEventType}, serde::Raw, DeviceId, TransactionId, UserId, }; @@ -666,7 +666,7 @@ impl Client { // have with this user. let mut content = self .store() - .get_account_data_event(EventType::Direct) + .get_account_data_event(GlobalAccountDataEventType::Direct) .await? .map(|e| e.deserialize()) .transpose()? diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 27f45a653..b0218114d 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -18,7 +18,7 @@ use ruma::{ events::{ room::history_visibility::HistoryVisibility, tag::{TagInfo, TagName}, - AnyStateEvent, AnySyncStateEvent, EventType, + AnyStateEvent, AnySyncStateEvent, StateEventType, }, serde::Raw, uint, EventId, RoomId, UInt, UserId, @@ -640,7 +640,7 @@ impl Common { /// Get all state events of a given type in this room. pub async fn get_state_events( &self, - event_type: EventType, + event_type: StateEventType, ) -> Result>> { self.client.store().get_state_events(self.room_id(), event_type).await.map_err(Into::into) } @@ -648,7 +648,7 @@ impl Common { /// Get a specific state event in this room. pub async fn get_state_event( &self, - event_type: EventType, + event_type: StateEventType, state_key: &str, ) -> Result>> { self.client From 692f95da79ff42676dca49b81ff1a6898afc4fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Tue, 8 Mar 2022 11:53:17 +0100 Subject: [PATCH 06/36] sled: Add method to create CryptoStore from StateStore Allow to use the same database for both stores --- crates/matrix-sdk-sled/src/state_store.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index f03181dfe..db9838ece 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -59,6 +59,9 @@ use sled::{ use tokio::task::spawn_blocking; use tracing::{info, warn}; +#[cfg(feature = "encryption")] +pub use crate::CryptoStore; + #[derive(Debug, Serialize, Deserialize)] pub enum DatabaseType { Unencrypted, @@ -377,6 +380,14 @@ impl SledStore { SledStore::open_helper(db, Some(path), None) } + #[cfg(feature = "encryption")] + /// Open a `CryptoStore` that uses the same database as this store. + /// + /// The given passphrase will be used to encrypt private data. + pub fn get_crypto_store(&self, passphrase: Option<&str>) -> Result { + CryptoStore::open_with_database(self.inner.clone(), passphrase) + } + fn serialize_event(&self, event: &impl Serialize) -> Result, SledStoreError> { if let Some(key) = &*self.store_key { let encrypted = key.encrypt(event)?; From 4d41f941995f15f72c9e39bf9cf5f191f6a9ccd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Tue, 8 Mar 2022 12:26:25 +0100 Subject: [PATCH 07/36] sled: Add helper to open stores for encryption --- crates/matrix-sdk-sled/src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index 8b97ccbe4..e7c8eface 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "encryption")] +use std::path::Path; + #[cfg(feature = "encryption")] mod cryptostore; mod state_store; @@ -5,3 +8,21 @@ mod state_store; #[cfg(feature = "encryption")] pub use cryptostore::SledStore as CryptoStore; pub use state_store::SledStore as StateStore; + +#[cfg(feature = "encryption")] +/// Create a [`StateStore`] and a [`CryptoStore`] that use the same database and +/// passphrase. +pub fn open_stores_with_path( + path: impl AsRef, + passphrase: Option<&str>, +) -> Result<(Box, Box), anyhow::Error> { + if let Some(passphrase) = passphrase { + let state_store = StateStore::open_with_passphrase(path, passphrase)?; + let crypto_store = state_store.get_crypto_store(Some(passphrase))?; + Ok((Box::new(state_store), Box::new(crypto_store))) + } else { + let state_store = StateStore::open_with_path(path)?; + let crypto_store = state_store.get_crypto_store(None)?; + Ok((Box::new(state_store), Box::new(crypto_store))) + } +} From 28a83da421b543bf85bb2e7281dc77441ef3130b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 12:26:34 +0100 Subject: [PATCH 08/36] sdk: Remove default store constructors --- crates/matrix-sdk/examples/autojoin.rs | 20 +++-- crates/matrix-sdk/examples/command_bot.rs | 21 ++++-- crates/matrix-sdk/src/client.rs | 19 ++--- crates/matrix-sdk/src/config/client.rs | 89 ++--------------------- crates/matrix-sdk/src/config/mod.rs | 2 +- 5 files changed, 43 insertions(+), 108 deletions(-) diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 4e311c429..2dfb92303 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -45,12 +45,22 @@ async fn login_and_sync( username: &str, password: &str, ) -> Result<(), matrix_sdk::Error> { - let mut home = dirs::home_dir().expect("no home directory found"); - home.push("autojoin_bot"); + let mut client_config = ClientConfig::new(); - let client_config = - ClientConfig::with_named_store(home.to_str().expect("home dir path must be utf-8"), None) - .await?; + #[cfg(feature = "sled_state_store")] + { + // The location to save files to + let mut home = dirs::home_dir().expect("no home directory found"); + home.push("autojoin_bot"); + let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; + client_config = client_config.state_store(Box::new(state_store)); + } + + #[cfg(feature = "indexeddb_stores")] + { + let state_store = matrix_sdk_indexeddb::StateStore::open(); + client_config = client_config.state_store(Box::new(state_store)); + } let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); let client = Client::new_with_config(homeserver_url, client_config).await.unwrap(); diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index 3f74ada43..90dc23b6f 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -37,13 +37,22 @@ async fn login_and_sync( username: String, password: String, ) -> Result<(), matrix_sdk::Error> { - // the location for `JsonStore` to save files to - let mut home = dirs::home_dir().expect("no home directory found"); - home.push("party_bot"); + let mut client_config = ClientConfig::new(); - let client_config = - ClientConfig::with_named_store(home.to_str().expect("home dir path must be utf-8"), None) - .await?; + #[cfg(feature = "sled_state_store")] + { + // The location to save files to + let mut home = dirs::home_dir().expect("no home directory found"); + home.push("party_bot"); + let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; + client_config = client_config.state_store(Box::new(state_store)); + } + + #[cfg(feature = "indexeddb_stores")] + { + let state_store = matrix_sdk_indexeddb::StateStore::open(); + client_config = client_config.state_store(Box::new(state_store)); + } let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); // create a new Client with the given homeserver url and config diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index b9be4d312..33888dc54 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -162,7 +162,7 @@ impl Client { /// /// * `homeserver_url` - The homeserver that the client should connect to. pub async fn new(homeserver_url: Url) -> Result { - let config = ClientConfig::new().await?; + let config = ClientConfig::new(); Client::new_with_config(homeserver_url, config).await } @@ -241,7 +241,7 @@ impl Client { /// /// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri pub async fn new_from_user_id(user_id: &UserId) -> Result { - let config = ClientConfig::new().await?; + let config = ClientConfig::new(); Client::new_from_user_id_with_config(user_id, config).await } @@ -2385,8 +2385,7 @@ pub(crate) mod test { device_id: device_id!("DEVICEID").to_owned(), }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let config = - ClientConfig::new().await.unwrap().request_config(RequestConfig::new().disable_retry()); + let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); let client = Client::new_with_config(homeserver, config).await.unwrap(); client.restore_login(session).await.unwrap(); @@ -2484,7 +2483,7 @@ pub(crate) mod test { #[async_test] async fn login_with_discovery() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::new().await.unwrap().use_discovery_response(); + let config = ClientConfig::new().use_discovery_response(); let client = Client::new_with_config(homeserver, config).await.unwrap(); @@ -2504,7 +2503,7 @@ pub(crate) mod test { #[async_test] async fn login_no_discovery() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::new().await.unwrap().use_discovery_response(); + let config = ClientConfig::new().use_discovery_response(); let client = Client::new_with_config(homeserver.clone(), config).await.unwrap(); @@ -2633,12 +2632,8 @@ pub(crate) mod test { let room = client.get_joined_room(room_id); assert!(room.is_some()); - // test store reloads with correct room state from the sled store - let path = tempfile::tempdir().unwrap(); - let config = ClientConfig::with_named_store(path.into_path().to_str().unwrap(), None) - .await - .unwrap() - .request_config(RequestConfig::new().disable_retry()); + // test store reloads with correct room state from the state store + let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); let joined_client = Client::new_with_config(homeserver, config).await.unwrap(); joined_client.restore_login(session).await.unwrap(); diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 8639c7953..630275e48 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -37,7 +37,7 @@ use crate::{config::RequestConfig, HttpSend, Result}; /// // verification /// /// # futures::executor::block_on(async { -/// let client_config = ClientConfig::new().await? +/// let client_config = ClientConfig::new() /// .proxy("http://localhost:8080")? /// .disable_ssl_verification(); /// # matrix_sdk::Result::<()>::Ok(()) @@ -61,7 +61,7 @@ use crate::{config::RequestConfig, HttpSend, Result}; /// .user_agent("MyApp/v3.0"); /// /// # futures::executor::block_on(async { -/// let client_config = ClientConfig::new().await? +/// let client_config = ClientConfig::new() /// .client(Arc::new(builder.build()?)); /// # matrix_sdk::Result::<()>::Ok(()) /// # }); @@ -94,89 +94,10 @@ impl Debug for ClientConfig { } } -#[cfg(feature = "sled_state_store")] -mod store_helpers { - use matrix_sdk_sled::StateStore; - - use super::Result; - - /// Build the sled Store with the default settings - as a temporary storage - pub async fn default_store() -> Result> { - Ok(Box::new(StateStore::open()?)) - } - - /// Build a sled store at `name` (being a relative or full path), and open - /// the store with the given passphrase (if given) for encryption - pub async fn default_store_with_name( - name: &str, - passphrase: Option<&str>, - ) -> Result> { - Ok(Box::new(match passphrase { - Some(pass) => StateStore::open_with_passphrase(name, pass)?, - _ => StateStore::open_with_path(&name)?, - })) - } -} - -#[cfg(feature = "indexeddb_stores")] -mod store_helpers { - use matrix_sdk_indexeddb::StateStore; - - use super::Result; - - /// Open the IndexedDB store with the default name, unencrypted - pub async fn default_store() -> Result> { - Ok(Box::new(StateStore::open().await?)) - } - - /// Open the indexeddb store at `name` (IndexedDB Database name), and open - /// the store with the given passphrase (if given) for encryption - pub async fn default_store_with_name( - name: &str, - passphrase: Option<&str>, - ) -> Result> { - Ok(Box::new(match passphrase { - Some(pass) => StateStore::open_with_passphrase(name.to_owned(), pass).await?, - _ => StateStore::open_with_name(name.to_owned()).await?, - })) - } -} - -#[cfg(not(any(feature = "indexeddb_stores", feature = "sled_state_store")))] -mod store_helpers { - use matrix_sdk_base::store::MemoryStore as StateStore; - - use super::Result; - /// Open a new in-memory StateStore - pub async fn default_store() -> Result> { - Ok(Box::new(StateStore::new())) - } - - /// Alias for `default_store` - in Memory Stores are never named - pub async fn default_store_with_name( - _name: &str, - _passphrase: Option<&str>, - ) -> Result> { - Ok(Box::new(StateStore::new())) - } -} - -pub use store_helpers::{default_store, default_store_with_name}; - impl ClientConfig { /// Create a new default `ClientConfig`. - pub async fn new() -> Result { - let mut d = Self::default(); - d.base_config = d.base_config.state_store(default_store().await?); - Ok(d) - } - - /// Create a new ClientConfig with a named state store, encrypted with the - /// given passphrase (if any) - pub async fn with_named_store(name: &str, passphrase: Option<&str>) -> Result { - let mut d = Self::default(); - d.base_config = d.base_config.state_store(default_store_with_name(name, passphrase).await?); - Ok(d) + pub fn new() -> Self { + Self::default() } /// Set the proxy through which all the HTTP requests should go. @@ -193,7 +114,7 @@ impl ClientConfig { /// # futures::executor::block_on(async { /// use matrix_sdk::{Client, config::ClientConfig}; /// - /// let client_config = ClientConfig::new().await? + /// let client_config = ClientConfig::new() /// .proxy("http://localhost:8080")?; /// /// # Result::<_, matrix_sdk::Error>::Ok(()) diff --git a/crates/matrix-sdk/src/config/mod.rs b/crates/matrix-sdk/src/config/mod.rs index 533d0cdb6..48830e78f 100644 --- a/crates/matrix-sdk/src/config/mod.rs +++ b/crates/matrix-sdk/src/config/mod.rs @@ -20,6 +20,6 @@ mod client; mod request; mod sync; -pub use client::{default_store, default_store_with_name, ClientConfig}; +pub use client::ClientConfig; pub use request::RequestConfig; pub use sync::SyncSettings; From 0ea12b3b4aee8a7064c329fd75742c5240191e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 13:38:34 +0100 Subject: [PATCH 09/36] base: Separate store config from the client config --- crates/matrix-sdk-base/src/client.rs | 29 +++++++++---- crates/matrix-sdk-base/src/store/mod.rs | 50 +++++++++++++++++++++++ crates/matrix-sdk/examples/autojoin.rs | 3 ++ crates/matrix-sdk/examples/command_bot.rs | 3 ++ crates/matrix-sdk/src/config/client.rs | 10 ++++- 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index d4dd804d5..c2f6c452c 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -70,6 +70,7 @@ use crate::{ session::Session, store::{ ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, StateStore, Store, + StoreConfig, }, }; @@ -113,9 +114,7 @@ impl fmt::Debug for BaseClient { /// ``` #[derive(Default)] pub struct BaseClientConfig { - #[cfg(feature = "encryption")] - crypto_store: Option>, - state_store: Option>, + store_config: StoreConfig, } #[cfg(not(tarpaulin_include))] @@ -132,18 +131,30 @@ impl BaseClientConfig { Default::default() } + /// Create a new `BaseClientConfig` with the given `StoreConfig`. + #[must_use] + pub fn with_store_config(store_config: StoreConfig) -> Self { + Self { store_config } + } + + /// Get the `StoreConfig` used by this `BaseClientConfig`. + #[cfg(feature = "encryption")] + pub fn get_store_config(&self) -> &StoreConfig { + &self.store_config + } + /// Set a custom implementation of a `CryptoStore`. /// /// The crypto store should be opened before being set. #[cfg(feature = "encryption")] pub fn crypto_store(mut self, store: Box) -> Self { - self.crypto_store = Some(store); + self.store_config = self.store_config.crypto_store(store); self } /// Set a custom implementation of a `StateStore`. pub fn state_store(mut self, store: Box) -> Self { - self.state_store = Some(store); + self.store_config = self.store_config.state_store(store); self } } @@ -200,9 +211,13 @@ impl BaseClient { /// * `config` - An optional session if the user already has one from a /// previous login call. pub async fn new_with_config(config: BaseClientConfig) -> Result { - let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store); + let store = config + .store_config + .state_store + .map(Store::new) + .unwrap_or_else(Store::open_memory_store); #[cfg(feature = "encryption")] - let holder = config.crypto_store.map(CryptoHolder::new).unwrap_or_default(); + let holder = config.store_config.crypto_store.map(CryptoHolder::new).unwrap_or_default(); Ok(BaseClient { session: store.session.clone(), diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 5185bf9e3..8b403dd2a 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -24,6 +24,7 @@ use std::{ collections::{BTreeMap, BTreeSet}, ops::Deref, pin::Pin, + result::Result as StdResult, sync::Arc, }; @@ -33,6 +34,8 @@ pub mod integration_tests; use dashmap::DashMap; use matrix_sdk_common::{async_trait, locks::RwLock, AsyncTraitDeps}; +#[cfg(feature = "encryption")] +use matrix_sdk_crypto::store::CryptoStore; use ruma::{ api::client::push::get_notifications::v3::Notification, events::{ @@ -606,3 +609,50 @@ impl StateChanges { self.timeline.insert(room_id.to_owned(), timeline); } } + +/// Configuration for the state store and, when `encryption` is enabled, for the +/// crypto store. +/// +/// # Example +/// +/// ``` +/// # use matrix_sdk_base::store::StoreConfig; +/// +/// let store_config = StoreConfig::new(); +/// ``` +#[derive(Default)] +pub struct StoreConfig { + #[cfg(feature = "encryption")] + pub(crate) crypto_store: Option>, + pub(crate) state_store: Option>, +} + +#[cfg(not(tarpaulin_include))] +impl std::fmt::Debug for StoreConfig { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { + fmt.debug_struct("StoreConfig").finish() + } +} + +impl StoreConfig { + /// Create a new default `StoreConfig`. + #[must_use] + pub fn new() -> Self { + Default::default() + } + + /// Set a custom implementation of a `CryptoStore`. + /// + /// The crypto store should be opened before being set. + #[cfg(feature = "encryption")] + pub fn crypto_store(mut self, store: Box) -> Self { + self.crypto_store = Some(store); + self + } + + /// Set a custom implementation of a `StateStore`. + pub fn state_store(mut self, store: Box) -> Self { + self.state_store = Some(store); + self + } +} diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 2dfb92303..865460db1 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -45,6 +45,9 @@ async fn login_and_sync( username: &str, password: &str, ) -> Result<(), matrix_sdk::Error> { + #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_stores")))] + let client_config = ClientConfig::new(); + #[cfg(any(feature = "sled_state_store", feature = "indexeddb_stores"))] let mut client_config = ClientConfig::new(); #[cfg(feature = "sled_state_store")] diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index 90dc23b6f..c5da4b604 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -37,6 +37,9 @@ async fn login_and_sync( username: String, password: String, ) -> Result<(), matrix_sdk::Error> { + #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_stores")))] + let client_config = ClientConfig::new(); + #[cfg(any(feature = "sled_state_store", feature = "indexeddb_stores"))] let mut client_config = ClientConfig::new(); #[cfg(feature = "sled_state_store")] diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 630275e48..ba261f729 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -20,7 +20,7 @@ use std::{ }; use http::header::InvalidHeaderValue; -use matrix_sdk_base::{BaseClientConfig, StateStore}; +use matrix_sdk_base::{store::StoreConfig, BaseClientConfig, StateStore}; use crate::{config::RequestConfig, HttpSend, Result}; @@ -100,6 +100,14 @@ impl ClientConfig { Self::default() } + /// Create a new `ClientConfig` with the given `StoreConfig`. + pub fn with_store_config(store_config: StoreConfig) -> Self { + Self { + base_config: BaseClientConfig::with_store_config(store_config), + ..Default::default() + } + } + /// Set the proxy through which all the HTTP requests should go. /// /// Note, only HTTP proxies are supported. From 4b7f05e91349ce41696ce907b8dbd86def409da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 13:53:09 +0100 Subject: [PATCH 10/36] sled: Add a method to create a StoreConfig --- crates/matrix-sdk-sled/Cargo.toml | 2 +- crates/matrix-sdk-sled/src/lib.rs | 33 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-sled/Cargo.toml b/crates/matrix-sdk-sled/Cargo.toml index 8fd3bee6b..1b1c4e938 100644 --- a/crates/matrix-sdk-sled/Cargo.toml +++ b/crates/matrix-sdk-sled/Cargo.toml @@ -11,7 +11,7 @@ required-features = ["binary-build"] [features] default = ["encryption"] -encryption = ["matrix-sdk-crypto"] +encryption = ["matrix-sdk-base/encryption", "matrix-sdk-crypto"] binary-build = [ "atty", "clap", diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index e7c8eface..c3b57d15f 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -1,6 +1,7 @@ -#[cfg(feature = "encryption")] use std::path::Path; +use matrix_sdk_base::store::StoreConfig; + #[cfg(feature = "encryption")] mod cryptostore; mod state_store; @@ -26,3 +27,33 @@ pub fn open_stores_with_path( Ok((Box::new(state_store), Box::new(crypto_store))) } } + +/// Create a [`StoreConfig`] with an opened sled [`StateStore`] that uses the +/// given path and passphrase. If `encryption` is enabled, a [`CryptoStore`] +/// with the same parameters is also opened. +pub fn make_config( + path: impl AsRef, + passphrase: Option<&str>, +) -> Result { + let mut config = StoreConfig::new(); + + #[cfg(feature = "encryption")] + { + let (state_store, crypto_store) = open_stores_with_path(path, passphrase)?; + config = config.state_store(state_store); + config = config.crypto_store(crypto_store); + } + + #[cfg(not(feature = "encryption"))] + { + let state_store = if let Some(passphrase) = passphrase { + StateStore::open_with_passphrase(path, passphrase)? + } else { + StateStore::open_with_path(path)? + }; + + config = config.state_store(Box::new(state_store)); + } + + Ok(config) +} From 0327b4f8fc197df2a749c21ca140fbecad684dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 14:30:49 +0100 Subject: [PATCH 11/36] indexeddb: Add methods to create a StoreConfig --- crates/matrix-sdk-indexeddb/Cargo.toml | 2 +- crates/matrix-sdk-indexeddb/src/lib.rs | 56 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index 9e0d6d918..06c7b1168 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [features] default = ["encryption"] -encryption = ["matrix-sdk-crypto"] +encryption = ["matrix-sdk-base/encryption", "matrix-sdk-crypto"] [package.metadata.docs.rs] default-target = "wasm32-unknown-unknown" diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index 8ecfae46d..db5056035 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(target_arch = "wasm32")] +use matrix_sdk_base::store::StoreConfig; + mod safe_encode; #[cfg(target_arch = "wasm32")] @@ -12,3 +15,56 @@ mod cryptostore; pub use cryptostore::IndexeddbStore as CryptoStore; #[cfg(target_arch = "wasm32")] pub use state_store::IndexeddbStore as StateStore; + +#[cfg(target_arch = "wasm32")] +#[cfg(feature = "encryption")] +/// Create a [`StateStore`] and a [`CryptoStore`] that use the same name and +/// passphrase. +pub async fn open_stores_with_name( + name: impl Into, + passphrase: Option<&str>, +) -> Result<(Box, Box), anyhow::Error> { + let name = name.into(); + + if let Some(passphrase) = passphrase { + let state_store = StateStore::open_with_passphrase(name.clone(), passphrase).await?; + let crypto_store = CryptoStore::open_with_passphrase(name, passphrase).await?; + Ok((Box::new(state_store), Box::new(crypto_store))) + } else { + let state_store = StateStore::open_with_name(name.clone()).await?; + let crypto_store = CryptoStore::open_with_name(name).await?; + Ok((Box::new(state_store), Box::new(crypto_store))) + } +} + +#[cfg(target_arch = "wasm32")] +/// Create a [`StoreConfig`] with an opened indexeddb [`StateStore`] that uses +/// the given name and passphrase. If `encryption` is enabled, a [`CryptoStore`] +/// with the same parameters is also opened. +pub async fn make_config( + name: impl Into, + passphrase: Option<&str>, +) -> Result { + let mut config = StoreConfig::new(); + let name = name.into(); + + #[cfg(feature = "encryption")] + { + let (state_store, crypto_store) = open_stores_with_name(name, passphrase).await?; + config = config.state_store(state_store); + config = config.crypto_store(crypto_store); + } + + #[cfg(not(feature = "encryption"))] + { + let state_store = if let Some(passphrase) = passphrase { + StateStore::open_with_passphrase(name, passphrase).await? + } else { + StateStore::open_with_name(name).await? + }; + + config = config.state_store(Box::new(state_store)); + } + + Ok(config) +} From 0f60dfcfc580b0bd467300bc1f90b142b5eb5b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 14:40:11 +0100 Subject: [PATCH 12/36] sdk: Re-export stores and store config --- crates/matrix-sdk/src/config/client.rs | 7 +++++-- crates/matrix-sdk/src/config/mod.rs | 1 + crates/matrix-sdk/src/lib.rs | 1 + crates/matrix-sdk/src/store.rs | 22 ++++++++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 crates/matrix-sdk/src/store.rs diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index ba261f729..66f920e79 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -20,9 +20,12 @@ use std::{ }; use http::header::InvalidHeaderValue; -use matrix_sdk_base::{store::StoreConfig, BaseClientConfig, StateStore}; +use matrix_sdk_base::{BaseClientConfig, StateStore}; -use crate::{config::RequestConfig, HttpSend, Result}; +use crate::{ + config::{RequestConfig, StoreConfig}, + HttpSend, Result, +}; /// Configuration for the creation of the `Client`. /// diff --git a/crates/matrix-sdk/src/config/mod.rs b/crates/matrix-sdk/src/config/mod.rs index 48830e78f..6f759d643 100644 --- a/crates/matrix-sdk/src/config/mod.rs +++ b/crates/matrix-sdk/src/config/mod.rs @@ -21,5 +21,6 @@ mod request; mod sync; pub use client::ClientConfig; +pub use matrix_sdk_base::store::StoreConfig; pub use request::RequestConfig; pub use sync::SyncSettings; diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index ad552656a..bb0c55640 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -50,6 +50,7 @@ mod http_client; /// High-level room API pub mod room; mod room_member; +pub mod store; mod sync; #[cfg(feature = "encryption")] diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs new file mode 100644 index 000000000..25fcc90be --- /dev/null +++ b/crates/matrix-sdk/src/store.rs @@ -0,0 +1,22 @@ +// Copyright 2022 Kévin Commaille +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions and types to initialize a store. +//! +//! The re-exports present here depend on the store-related features that are +//! enabled. + +#[cfg(feature = "indexeddb_stores")] +pub use matrix_sdk_indexeddb::*; +#[cfg(any(feature = "sled_state_store", feature = "sled_cryptostore"))] +pub use matrix_sdk_sled::*; From 8c7bbb0e07442608550ec94734ed135573f17f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 9 Mar 2022 15:47:01 +0100 Subject: [PATCH 13/36] sdk: Update store setup instructions for encryption. --- crates/matrix-sdk/src/config/client.rs | 28 +++++++++++++++++++++++- crates/matrix-sdk/src/docs/encryption.md | 10 +++++++-- crates/matrix-sdk/src/store.rs | 15 ++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 66f920e79..dae650f2b 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -103,7 +103,33 @@ impl ClientConfig { Self::default() } - /// Create a new `ClientConfig` with the given `StoreConfig`. + /// Create a new `ClientConfig` with the given [`StoreConfig`]. + /// + /// The easiest way to get a [`StoreConfig`] is to use the [`make_config`] + /// method from the [`store`] module or directly from one of the store + /// crates. + /// + /// # Arguments + /// + /// * `store_config` - The configuration of the store. + /// + /// # Example + /// + /// ``` + /// # futures::executor::block_on(async { + /// # use matrix_sdk_base::store::MemoryStore; + /// # let custom_state_store = Box::new(MemoryStore::new()); + /// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}}; + /// + /// let store_config = StoreConfig::new().state_store(custom_state_store); + /// let client_config = ClientConfig::with_store_config(store_config) + /// .use_discovery_response(); + /// + /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # }); + /// ``` + /// [`make_config`]: crate::store::make_config + /// [`store`]: crate::store pub fn with_store_config(store_config: StoreConfig) -> Self { Self { base_config: BaseClientConfig::with_store_config(store_config), diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 3757fd5b1..5f6c2db07 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -165,8 +165,10 @@ unverified devices, verifying devices is **not** necessary for encryption to work. 1. Make sure the `encryption` feature is enabled. -2. Ensure you have a persistent storage backend, either by activating the - `sled_state_store`-feature or providing one via [`ClientConfig.state_store`] +2. To persist the encryption keys, you can use one of the provided backend +constructors as described in the documentation of the [`store`] module or you +can provide your own backend that implements [`CryptoStore`] in a +[`StoreConfig`] or via [`ClientConfig::crypto_store()`]. ## Restoring a client @@ -228,3 +230,7 @@ is **not** supported using the default store. [Restoring a Client]: #restoring-a-client [spec]: https://spec.matrix.org/unstable/client-server-api/#relationship-between-access-tokens-and-devices [device keys]: https://spec.matrix.org/unstable/client-server-api/#device-keys +[`store`]: crate::store +[`CryptoStore`]: matrix_sdk_base::crypto::store::CryptoStore +[`StoreConfig`]: crate::config::StoreConfig +[`ClientConfig::crypto_store()`]: crate::config::ClientConfig::crypto_store() diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index 25fcc90be..12512fd23 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -14,7 +14,20 @@ //! Functions and types to initialize a store. //! //! The re-exports present here depend on the store-related features that are -//! enabled. +//! enabled: +//! +//! 1. `sled_state_store` provides a `StateStore`, while +//! `sled_cryptostore` provides also a `CryptoStore` for encryption data. This +//! is the default persistent store implementation for non-WebAssembly. +//! 2. `indexeddb_store` provides both a `StateStore` and a `CryptoStore` if +//! `encryption` is also enabled. This is the default persistent store +//! implementation for WebAssembly. +//! +//! Both options provide a `make_config` convenience method to create a +//! [`StoreConfig`] for [`ClientConfig::with_store_config()`]. +//! +//! [`StoreConfig`]: crate::config::StoreConfig +//! [`ClientConfig::with_store_config()`]: crate::config::ClientConfig::with_store_config() #[cfg(feature = "indexeddb_stores")] pub use matrix_sdk_indexeddb::*; From ad31540b101e550ea261d108aa59784a2d2e9922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 11:27:35 +0100 Subject: [PATCH 14/36] sdk: Don't enable store encryption by default --- crates/matrix-sdk/Cargo.toml | 9 +++++---- crates/matrix-sdk/examples/autojoin.rs | 6 +++--- crates/matrix-sdk/examples/command_bot.rs | 6 +++--- crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml | 4 ++-- crates/matrix-sdk/src/store.rs | 2 +- xtask/src/ci.rs | 4 ++-- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 4b2d4ccd9..dd95cfd6b 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -24,12 +24,13 @@ default = [ "native-tls" ] -indexeddb_stores = ["matrix-sdk-indexeddb"] +indexeddb_state_store = ["matrix-sdk-indexeddb"] +indexeddb_cryptostore = ["matrix-sdk-indexeddb/encryption", "encryption"] encryption = ["matrix-sdk-base/encryption"] qrcode = ["encryption", "matrix-sdk-base/qrcode"] # TODO merge those two sled features sled_state_store = ["matrix-sdk-sled"] -sled_cryptostore = ["matrix-sdk-sled", "encryption"] +sled_cryptostore = ["matrix-sdk-sled/encryption", "encryption"] markdown = ["ruma/markdown"] native-tls = ["reqwest/native-tls"] rustls-tls = ["reqwest/rustls-tls"] @@ -69,8 +70,8 @@ url = "2.2.2" zeroize = "1.3.0" async-stream = "0.3.2" -matrix-sdk-sled = { path = "../matrix-sdk-sled", optional = true } -matrix-sdk-indexeddb = { path = "../matrix-sdk-indexeddb", optional = true } +matrix-sdk-sled = { path = "../matrix-sdk-sled", default-features = false, optional = true } +matrix-sdk-indexeddb = { path = "../matrix-sdk-indexeddb", default-features = false, optional = true } [dependencies.image] version = "0.24.0" diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 865460db1..82046bae4 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -45,9 +45,9 @@ async fn login_and_sync( username: &str, password: &str, ) -> Result<(), matrix_sdk::Error> { - #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_stores")))] + #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))] let client_config = ClientConfig::new(); - #[cfg(any(feature = "sled_state_store", feature = "indexeddb_stores"))] + #[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))] let mut client_config = ClientConfig::new(); #[cfg(feature = "sled_state_store")] @@ -59,7 +59,7 @@ async fn login_and_sync( client_config = client_config.state_store(Box::new(state_store)); } - #[cfg(feature = "indexeddb_stores")] + #[cfg(feature = "indexeddb_state_store")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); client_config = client_config.state_store(Box::new(state_store)); diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index c5da4b604..f5f930747 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -37,9 +37,9 @@ async fn login_and_sync( username: String, password: String, ) -> Result<(), matrix_sdk::Error> { - #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_stores")))] + #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))] let client_config = ClientConfig::new(); - #[cfg(any(feature = "sled_state_store", feature = "indexeddb_stores"))] + #[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))] let mut client_config = ClientConfig::new(); #[cfg(feature = "sled_state_store")] @@ -51,7 +51,7 @@ async fn login_and_sync( client_config = client_config.state_store(Box::new(state_store)); } - #[cfg(feature = "indexeddb_stores")] + #[cfg(feature = "indexeddb_state_store")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); client_config = client_config.state_store(Box::new(state_store)); diff --git a/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml b/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml index ab735e69e..519bccc07 100644 --- a/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml +++ b/crates/matrix-sdk/examples/wasm_command_bot/Cargo.toml @@ -25,9 +25,9 @@ getrandom = { version = "0.2.4", features = ["js"] } [dependencies.matrix-sdk] path = "../.." default-features = false -features = ["native-tls", "encryption", "indexeddb_stores"] +features = ["native-tls", "encryption", "indexeddb_state_store", "indexeddb_cryptostore"] [workspace] [dev-dependencies] -wasm-bindgen-test = "0.2" \ No newline at end of file +wasm-bindgen-test = "0.2" diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index 12512fd23..cebe82a6b 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -29,7 +29,7 @@ //! [`StoreConfig`]: crate::config::StoreConfig //! [`ClientConfig::with_store_config()`]: crate::config::ClientConfig::with_store_config() -#[cfg(feature = "indexeddb_stores")] +#[cfg(any(feature = "indexeddb_state_store", feature = "indexeddb_cryptostore"))] pub use matrix_sdk_indexeddb::*; #[cfg(any(feature = "sled_state_store", feature = "sled_cryptostore"))] pub use matrix_sdk_sled::*; diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 824b822e2..593874a4e 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -187,14 +187,14 @@ fn run_wasm_checks(cmd: Option) -> Result<()> { WasmFeatureSet::MatrixSdkNoDefault, "-p matrix-sdk \ --no-default-features \ - --features qrcode,encryption,indexeddb_stores,rustls-tls", + --features qrcode,encryption,indexeddb_state_store,indexeddb_cryptostore,rustls-tls", ), (WasmFeatureSet::MatrixSdkBase, "-p matrix-sdk-base"), (WasmFeatureSet::MatrixSdkCommon, "-p matrix-sdk-common"), (WasmFeatureSet::MatrixSdkCrypto, "-p matrix-sdk-crypto"), ( WasmFeatureSet::MatrixSdkIndexeddbStores, - "-p matrix-sdk --no-default-features --features indexeddb_stores,encryption,rustls-tls", + "-p matrix-sdk --no-default-features --features indexeddb_state_store,indexeddb_cryptostore,encryption,rustls-tls", ), ]); From 54c8b4f8bb2bbc897f9cb55eb5b8ffb12689df0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 14:00:32 +0100 Subject: [PATCH 15/36] sdk-base: Remove BaseClientConfig --- crates/matrix-sdk-base/src/client.rs | 93 ++++++-------------------- crates/matrix-sdk-base/src/lib.rs | 2 +- crates/matrix-sdk/src/client.rs | 2 +- crates/matrix-sdk/src/config/client.rs | 13 ++-- 4 files changed, 26 insertions(+), 84 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index c2f6c452c..5c2d5a8d9 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -13,15 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[allow(unused_imports)] -#[cfg(feature = "encryption")] -use std::ops::Deref; use std::{ collections::{BTreeMap, BTreeSet}, convert::TryFrom, fmt, sync::Arc, }; +#[allow(unused_imports)] +#[cfg(feature = "encryption")] +use std::{ops::Deref, result::Result as StdResult}; #[cfg(feature = "encryption")] use matrix_sdk_common::locks::Mutex; @@ -69,8 +69,7 @@ use crate::{ rooms::{Room, RoomInfo, RoomType}, session::Session, store::{ - ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, StateStore, Store, - StoreConfig, + ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, Store, StoreConfig, }, }; @@ -103,62 +102,6 @@ impl fmt::Debug for BaseClient { } } -/// Configuration for the creation of the `BaseClient`. -/// -/// # Example -/// -/// ``` -/// # use matrix_sdk_base::BaseClientConfig; -/// -/// let client_config = BaseClientConfig::new(); -/// ``` -#[derive(Default)] -pub struct BaseClientConfig { - store_config: StoreConfig, -} - -#[cfg(not(tarpaulin_include))] -impl std::fmt::Debug for BaseClientConfig { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - fmt.debug_struct("BaseClientConfig").finish() - } -} - -impl BaseClientConfig { - /// Create a new default `BaseClientConfig`. - #[must_use] - pub fn new() -> Self { - Default::default() - } - - /// Create a new `BaseClientConfig` with the given `StoreConfig`. - #[must_use] - pub fn with_store_config(store_config: StoreConfig) -> Self { - Self { store_config } - } - - /// Get the `StoreConfig` used by this `BaseClientConfig`. - #[cfg(feature = "encryption")] - pub fn get_store_config(&self) -> &StoreConfig { - &self.store_config - } - - /// Set a custom implementation of a `CryptoStore`. - /// - /// The crypto store should be opened before being set. - #[cfg(feature = "encryption")] - pub fn crypto_store(mut self, store: Box) -> Self { - self.store_config = self.store_config.crypto_store(store); - self - } - - /// Set a custom implementation of a `StateStore`. - pub fn state_store(mut self, store: Box) -> Self { - self.store_config = self.store_config.state_store(store); - self - } -} - #[cfg(feature = "encryption")] enum CryptoHolder { PreSetupStore(Option>), @@ -210,29 +153,25 @@ impl BaseClient { /// /// * `config` - An optional session if the user already has one from a /// previous login call. - pub async fn new_with_config(config: BaseClientConfig) -> Result { - let store = config - .store_config - .state_store - .map(Store::new) - .unwrap_or_else(Store::open_memory_store); + pub fn new_with_store_config(config: StoreConfig) -> Self { + let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store); #[cfg(feature = "encryption")] - let holder = config.store_config.crypto_store.map(CryptoHolder::new).unwrap_or_default(); + let holder = config.crypto_store.map(CryptoHolder::new).unwrap_or_default(); - Ok(BaseClient { + BaseClient { session: store.session.clone(), sync_token: store.sync_token.clone(), store, #[cfg(feature = "encryption")] olm: Mutex::new(holder).into(), - }) + } } } impl BaseClient { /// Create a new default client. - pub async fn new() -> Result { - BaseClient::new_with_config(BaseClientConfig::default()).await + pub fn new() -> Self { + BaseClient::new_with_store_config(StoreConfig::default()) } /// The current client session containing our user id, device id and access /// token. @@ -1122,7 +1061,7 @@ impl BaseClient { /// # use futures::executor::block_on; /// # let alice = user_id!("@alice:example.org").to_owned(); /// # block_on(async { - /// # let client = BaseClient::new().await.unwrap(); + /// # let client = BaseClient::new(); /// let device = client.get_device(&alice, device_id!("DEVICEID")).await; /// /// println!("{:?}", device); @@ -1176,7 +1115,7 @@ impl BaseClient { /// # use futures::executor::block_on; /// # let alice = user_id!("@alice:example.org"); /// # block_on(async { - /// # let client = BaseClient::new().await.unwrap(); + /// # let client = BaseClient::new(); /// let devices = client.get_user_devices(alice).await.unwrap(); /// /// for device in devices.devices() { @@ -1324,5 +1263,11 @@ impl BaseClient { } } +impl Default for BaseClient { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod test {} diff --git a/crates/matrix-sdk-base/src/lib.rs b/crates/matrix-sdk-base/src/lib.rs index 428a1c558..c02ca6af3 100644 --- a/crates/matrix-sdk-base/src/lib.rs +++ b/crates/matrix-sdk-base/src/lib.rs @@ -33,7 +33,7 @@ mod session; pub mod store; mod timeline_stream; -pub use client::{BaseClient, BaseClientConfig}; +pub use client::BaseClient; #[cfg(any(test, feature = "testing"))] pub use http; #[cfg(feature = "encryption")] diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index 33888dc54..d036a47af 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -183,7 +183,7 @@ impl Client { Arc::new(client_with_config(&config)?) }; - let base_client = BaseClient::new_with_config(config.base_config).await?; + let base_client = BaseClient::new_with_store_config(config.store_config); let session = base_client.session().clone(); let http_client = diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index dae650f2b..a67b03ad9 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -20,7 +20,7 @@ use std::{ }; use http::header::InvalidHeaderValue; -use matrix_sdk_base::{BaseClientConfig, StateStore}; +use matrix_sdk_base::StateStore; use crate::{ config::{RequestConfig, StoreConfig}, @@ -75,7 +75,7 @@ pub struct ClientConfig { pub(crate) proxy: Option, pub(crate) user_agent: Option, pub(crate) disable_ssl_verification: bool, - pub(crate) base_config: BaseClientConfig, + pub(crate) store_config: StoreConfig, pub(crate) request_config: RequestConfig, pub(crate) client: Option>, pub(crate) appservice_mode: bool, @@ -131,10 +131,7 @@ impl ClientConfig { /// [`make_config`]: crate::store::make_config /// [`store`]: crate::store pub fn with_store_config(store_config: StoreConfig) -> Self { - Self { - base_config: BaseClientConfig::with_store_config(store_config), - ..Default::default() - } + Self { store_config, ..Default::default() } } /// Set the proxy through which all the HTTP requests should go. @@ -180,7 +177,7 @@ impl ClientConfig { /// /// The state store should be opened before being set. pub fn state_store(mut self, store: Box) -> Self { - self.base_config = self.base_config.state_store(store); + self.store_config = self.store_config.state_store(store); self } @@ -226,7 +223,7 @@ impl ClientConfig { mut self, store: Box, ) -> Self { - self.base_config = self.base_config.crypto_store(store); + self.store_config = self.store_config.crypto_store(store); self } From d7673257b4c15536dcc7a7b8b3c4051c4b112497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 14:06:48 +0100 Subject: [PATCH 16/36] stores: Rename make_config to make_store_config --- crates/matrix-sdk-indexeddb/src/lib.rs | 4 ++-- crates/matrix-sdk-sled/src/lib.rs | 4 ++-- crates/matrix-sdk/src/config/client.rs | 8 ++++---- crates/matrix-sdk/src/store.rs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index db5056035..ac98ad047 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -20,7 +20,7 @@ pub use state_store::IndexeddbStore as StateStore; #[cfg(feature = "encryption")] /// Create a [`StateStore`] and a [`CryptoStore`] that use the same name and /// passphrase. -pub async fn open_stores_with_name( +async fn open_stores_with_name( name: impl Into, passphrase: Option<&str>, ) -> Result<(Box, Box), anyhow::Error> { @@ -41,7 +41,7 @@ pub async fn open_stores_with_name( /// Create a [`StoreConfig`] with an opened indexeddb [`StateStore`] that uses /// the given name and passphrase. If `encryption` is enabled, a [`CryptoStore`] /// with the same parameters is also opened. -pub async fn make_config( +async fn make_store_config( name: impl Into, passphrase: Option<&str>, ) -> Result { diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index c3b57d15f..ea02b950b 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -13,7 +13,7 @@ pub use state_store::SledStore as StateStore; #[cfg(feature = "encryption")] /// Create a [`StateStore`] and a [`CryptoStore`] that use the same database and /// passphrase. -pub fn open_stores_with_path( +fn open_stores_with_path( path: impl AsRef, passphrase: Option<&str>, ) -> Result<(Box, Box), anyhow::Error> { @@ -31,7 +31,7 @@ pub fn open_stores_with_path( /// Create a [`StoreConfig`] with an opened sled [`StateStore`] that uses the /// given path and passphrase. If `encryption` is enabled, a [`CryptoStore`] /// with the same parameters is also opened. -pub fn make_config( +pub fn make_store_config( path: impl AsRef, passphrase: Option<&str>, ) -> Result { diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index a67b03ad9..3a554f546 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -105,9 +105,9 @@ impl ClientConfig { /// Create a new `ClientConfig` with the given [`StoreConfig`]. /// - /// The easiest way to get a [`StoreConfig`] is to use the [`make_config`] - /// method from the [`store`] module or directly from one of the store - /// crates. + /// The easiest way to get a [`StoreConfig`] is to use the + /// [`make_store_config`] method from the [`store`] module or directly from + /// one of the store crates. /// /// # Arguments /// @@ -128,7 +128,7 @@ impl ClientConfig { /// # Result::<_, matrix_sdk::Error>::Ok(()) /// # }); /// ``` - /// [`make_config`]: crate::store::make_config + /// [`make_store_config`]: crate::store::make_store_config /// [`store`]: crate::store pub fn with_store_config(store_config: StoreConfig) -> Self { Self { store_config, ..Default::default() } diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index cebe82a6b..b31b58334 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -23,7 +23,7 @@ //! `encryption` is also enabled. This is the default persistent store //! implementation for WebAssembly. //! -//! Both options provide a `make_config` convenience method to create a +//! Both options provide a `make_store_config` convenience method to create a //! [`StoreConfig`] for [`ClientConfig::with_store_config()`]. //! //! [`StoreConfig`]: crate::config::StoreConfig From 81605b731d00d224d62abc3af8fc85cabadf96b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 14:09:38 +0100 Subject: [PATCH 17/36] sdk-base: Fix wording on StoreConfig docs --- crates/matrix-sdk-base/src/store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 8b403dd2a..7fb043cf6 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -643,7 +643,7 @@ impl StoreConfig { /// Set a custom implementation of a `CryptoStore`. /// - /// The crypto store should be opened before being set. + /// The crypto store must be opened before being set. #[cfg(feature = "encryption")] pub fn crypto_store(mut self, store: Box) -> Self { self.crypto_store = Some(store); From 597999acba0fef62347ccefb779b00eb944c79e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 14:26:27 +0100 Subject: [PATCH 18/36] sdk-base: Add constructors with stores for StoreConfig --- crates/matrix-sdk-base/src/store/mod.rs | 18 ++++++++++++++++++ crates/matrix-sdk-indexeddb/src/lib.rs | 8 ++------ crates/matrix-sdk-sled/src/lib.rs | 9 ++------- crates/matrix-sdk/src/config/client.rs | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 7fb043cf6..93d3bbca1 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -641,6 +641,24 @@ impl StoreConfig { Default::default() } + /// Create a new store config wrapping the given state store + pub fn new_with_state_store(state_store: Box) -> Self { + StoreConfig { + state_store: Some(state_store), + #[cfg(feature = "encryption")] + crypto_store: Default::default(), + } + } + + /// Create a new store config wrapping the given state and crypto store + #[cfg(feature = "encryption")] + pub fn new_with_state_and_crypto_store( + state_store: Box, + crypto_store: Box, + ) -> Self { + StoreConfig { state_store: Some(state_store), crypto_store: Some(crypto_store) } + } + /// Set a custom implementation of a `CryptoStore`. /// /// The crypto store must be opened before being set. diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index ac98ad047..1f425b94e 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -45,14 +45,12 @@ async fn make_store_config( name: impl Into, passphrase: Option<&str>, ) -> Result { - let mut config = StoreConfig::new(); let name = name.into(); #[cfg(feature = "encryption")] { let (state_store, crypto_store) = open_stores_with_name(name, passphrase).await?; - config = config.state_store(state_store); - config = config.crypto_store(crypto_store); + Ok(StoreConfig::new_with_state_and_crypto_store(state_store, crypto_store)) } #[cfg(not(feature = "encryption"))] @@ -63,8 +61,6 @@ async fn make_store_config( StateStore::open_with_name(name).await? }; - config = config.state_store(Box::new(state_store)); + Ok(StoreConfig::new_with_state_store(Box::new(state_store))) } - - Ok(config) } diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index ea02b950b..3647a4e91 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -35,13 +35,10 @@ pub fn make_store_config( path: impl AsRef, passphrase: Option<&str>, ) -> Result { - let mut config = StoreConfig::new(); - #[cfg(feature = "encryption")] { let (state_store, crypto_store) = open_stores_with_path(path, passphrase)?; - config = config.state_store(state_store); - config = config.crypto_store(crypto_store); + Ok(StoreConfig::new_with_state_and_crypto_store(state_store, crypto_store)) } #[cfg(not(feature = "encryption"))] @@ -52,8 +49,6 @@ pub fn make_store_config( StateStore::open_with_path(path)? }; - config = config.state_store(Box::new(state_store)); + Ok(StoreConfig::new_with_state_store(Box::new(state_store))) } - - Ok(config) } diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 3a554f546..7340aabef 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -121,7 +121,7 @@ impl ClientConfig { /// # let custom_state_store = Box::new(MemoryStore::new()); /// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}}; /// - /// let store_config = StoreConfig::new().state_store(custom_state_store); + /// let store_config = StoreConfig::new_with_state_store(custom_state_store); /// let client_config = ClientConfig::with_store_config(store_config) /// .use_discovery_response(); /// From 2bdc7541409c83a57bd025fe63906fc638a2e062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 16:55:12 +0100 Subject: [PATCH 19/36] ci: Lint matrix-sdk-crypto with a separate command --- xtask/src/ci.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 593874a4e..da626bdcd 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -112,11 +112,17 @@ fn check_typos() -> Result<()> { fn check_clippy() -> Result<()> { cmd!("rustup run nightly cargo clippy --all-targets -- -D warnings").run()?; cmd!( - "rustup run nightly cargo clippy --all-targets + "rustup run nightly cargo clippy --workspace --all-targets + --exclude matrix-sdk-crypto --exclude xtask --no-default-features --features native-tls,warp -- -D warnings" ) .run()?; + cmd!( + "rustup run nightly cargo clippy --all-targets -p matrix-sdk-crypto + --no-default-features -- -D warnings" + ) + .run()?; Ok(()) } From faa5cf54eb0738cc58e08d2ecb7ebfa95e4316c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 10 Mar 2022 17:42:37 +0100 Subject: [PATCH 20/36] indexeddb: Make make_store_config public --- crates/matrix-sdk-indexeddb/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index 1f425b94e..821006431 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -41,7 +41,7 @@ async fn open_stores_with_name( /// Create a [`StoreConfig`] with an opened indexeddb [`StateStore`] that uses /// the given name and passphrase. If `encryption` is enabled, a [`CryptoStore`] /// with the same parameters is also opened. -async fn make_store_config( +pub async fn make_store_config( name: impl Into, passphrase: Option<&str>, ) -> Result { From 27ee7c32ca5e732e56e1d7e7d4c9fb4501f4b9a6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 11:49:01 +0100 Subject: [PATCH 21/36] Remove new_with_* constructors for StoreConfig The regular builder pattern works just fine for the stores. --- crates/matrix-sdk-base/src/store/mod.rs | 18 ------------------ crates/matrix-sdk-indexeddb/src/lib.rs | 4 ++-- crates/matrix-sdk-sled/src/lib.rs | 4 ++-- crates/matrix-sdk/src/config/client.rs | 2 +- 4 files changed, 5 insertions(+), 23 deletions(-) diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 93d3bbca1..7fb043cf6 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -641,24 +641,6 @@ impl StoreConfig { Default::default() } - /// Create a new store config wrapping the given state store - pub fn new_with_state_store(state_store: Box) -> Self { - StoreConfig { - state_store: Some(state_store), - #[cfg(feature = "encryption")] - crypto_store: Default::default(), - } - } - - /// Create a new store config wrapping the given state and crypto store - #[cfg(feature = "encryption")] - pub fn new_with_state_and_crypto_store( - state_store: Box, - crypto_store: Box, - ) -> Self { - StoreConfig { state_store: Some(state_store), crypto_store: Some(crypto_store) } - } - /// Set a custom implementation of a `CryptoStore`. /// /// The crypto store must be opened before being set. diff --git a/crates/matrix-sdk-indexeddb/src/lib.rs b/crates/matrix-sdk-indexeddb/src/lib.rs index 821006431..c5e20bdd9 100644 --- a/crates/matrix-sdk-indexeddb/src/lib.rs +++ b/crates/matrix-sdk-indexeddb/src/lib.rs @@ -50,7 +50,7 @@ pub async fn make_store_config( #[cfg(feature = "encryption")] { let (state_store, crypto_store) = open_stores_with_name(name, passphrase).await?; - Ok(StoreConfig::new_with_state_and_crypto_store(state_store, crypto_store)) + Ok(StoreConfig::new().state_store(state_store).crypto_store(crypto_store)) } #[cfg(not(feature = "encryption"))] @@ -61,6 +61,6 @@ pub async fn make_store_config( StateStore::open_with_name(name).await? }; - Ok(StoreConfig::new_with_state_store(Box::new(state_store))) + Ok(StoreConfig::new().state_store(Box::new(state_store))) } } diff --git a/crates/matrix-sdk-sled/src/lib.rs b/crates/matrix-sdk-sled/src/lib.rs index 3647a4e91..89b56b5ca 100644 --- a/crates/matrix-sdk-sled/src/lib.rs +++ b/crates/matrix-sdk-sled/src/lib.rs @@ -38,7 +38,7 @@ pub fn make_store_config( #[cfg(feature = "encryption")] { let (state_store, crypto_store) = open_stores_with_path(path, passphrase)?; - Ok(StoreConfig::new_with_state_and_crypto_store(state_store, crypto_store)) + Ok(StoreConfig::new().state_store(state_store).crypto_store(crypto_store)) } #[cfg(not(feature = "encryption"))] @@ -49,6 +49,6 @@ pub fn make_store_config( StateStore::open_with_path(path)? }; - Ok(StoreConfig::new_with_state_store(Box::new(state_store))) + Ok(StoreConfig::new().state_store(Box::new(state_store))) } } diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 7340aabef..3a554f546 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -121,7 +121,7 @@ impl ClientConfig { /// # let custom_state_store = Box::new(MemoryStore::new()); /// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}}; /// - /// let store_config = StoreConfig::new_with_state_store(custom_state_store); + /// let store_config = StoreConfig::new().state_store(custom_state_store); /// let client_config = ClientConfig::with_store_config(store_config) /// .use_discovery_response(); /// From 48660c482fa86825364e1a77937ed3b2af8faa6d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 11:49:41 +0100 Subject: [PATCH 22/36] Use regular builder pattern for ClientConfig::store_config --- crates/matrix-sdk/src/config/client.rs | 8 +++++--- crates/matrix-sdk/src/store.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index 3a554f546..b4389b164 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -122,7 +122,8 @@ impl ClientConfig { /// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}}; /// /// let store_config = StoreConfig::new().state_store(custom_state_store); - /// let client_config = ClientConfig::with_store_config(store_config) + /// let client_config = ClientConfig::new() + /// .store_config(store_config) /// .use_discovery_response(); /// /// # Result::<_, matrix_sdk::Error>::Ok(()) @@ -130,8 +131,9 @@ impl ClientConfig { /// ``` /// [`make_store_config`]: crate::store::make_store_config /// [`store`]: crate::store - pub fn with_store_config(store_config: StoreConfig) -> Self { - Self { store_config, ..Default::default() } + pub fn store_config(mut self, store_config: StoreConfig) -> Self { + self.store_config = store_config; + self } /// Set the proxy through which all the HTTP requests should go. diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index b31b58334..60d123463 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -24,10 +24,10 @@ //! implementation for WebAssembly. //! //! Both options provide a `make_store_config` convenience method to create a -//! [`StoreConfig`] for [`ClientConfig::with_store_config()`]. +//! [`StoreConfig`] for [`ClientConfig::store_config()`]. //! //! [`StoreConfig`]: crate::config::StoreConfig -//! [`ClientConfig::with_store_config()`]: crate::config::ClientConfig::with_store_config() +//! [`ClientConfig::store_config()`]: crate::config::ClientConfig::store_config() #[cfg(any(feature = "indexeddb_state_store", feature = "indexeddb_cryptostore"))] pub use matrix_sdk_indexeddb::*; From e60b07336dca753a40f016d269dfefdd9f8c134b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:10:38 +0100 Subject: [PATCH 23/36] Rename Client::{new_with_config => with_config} --- crates/matrix-sdk/examples/autojoin.rs | 2 +- crates/matrix-sdk/examples/command_bot.rs | 2 +- crates/matrix-sdk/src/client.rs | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 82046bae4..490ce29d1 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -66,7 +66,7 @@ async fn login_and_sync( } let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - let client = Client::new_with_config(homeserver_url, client_config).await.unwrap(); + let client = Client::with_config(homeserver_url, client_config).await.unwrap(); client.login(username, password, None, Some("autojoin bot")).await?; diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index f5f930747..3028a1f37 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -59,7 +59,7 @@ async fn login_and_sync( let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); // create a new Client with the given homeserver url and config - let client = Client::new_with_config(homeserver_url, client_config).await.unwrap(); + let client = Client::with_config(homeserver_url, client_config).await.unwrap(); client.login(&username, &password, None, Some("command bot")).await?; diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index d036a47af..441e76a68 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -163,7 +163,7 @@ impl Client { /// * `homeserver_url` - The homeserver that the client should connect to. pub async fn new(homeserver_url: Url) -> Result { let config = ClientConfig::new(); - Client::new_with_config(homeserver_url, config).await + Client::with_config(homeserver_url, config).await } /// Create a new [`Client`] for the given homeserver and use the given @@ -174,7 +174,7 @@ impl Client { /// * `homeserver_url` - The homeserver that the client should connect to. /// /// * `config` - Configuration for the client. - pub async fn new_with_config(homeserver_url: Url, config: ClientConfig) -> Result { + pub async fn with_config(homeserver_url: Url, config: ClientConfig) -> Result { let homeserver = Arc::new(RwLock::new(homeserver_url)); let client = if let Some(client) = config.client { @@ -264,7 +264,7 @@ impl Client { config: ClientConfig, ) -> Result { let homeserver = Client::homeserver_from_user_id(user_id)?; - let client = Client::new_with_config(homeserver, config).await?; + let client = Client::with_config(homeserver, config).await?; let well_known = client.discover_homeserver().await?; let well_known = Url::parse(well_known.homeserver.base_url.as_ref())?; @@ -2386,7 +2386,7 @@ pub(crate) mod test { }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); - let client = Client::new_with_config(homeserver, config).await.unwrap(); + let client = Client::with_config(homeserver, config).await.unwrap(); client.restore_login(session).await.unwrap(); client @@ -2485,7 +2485,7 @@ pub(crate) mod test { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let config = ClientConfig::new().use_discovery_response(); - let client = Client::new_with_config(homeserver, config).await.unwrap(); + let client = Client::with_config(homeserver, config).await.unwrap(); let _m_login = mock("POST", "/_matrix/client/r0/login") .with_status(200) @@ -2505,7 +2505,7 @@ pub(crate) mod test { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let config = ClientConfig::new().use_discovery_response(); - let client = Client::new_with_config(homeserver.clone(), config).await.unwrap(); + let client = Client::with_config(homeserver.clone(), config).await.unwrap(); let _m_login = mock("POST", "/_matrix/client/r0/login") .with_status(200) @@ -2634,7 +2634,7 @@ pub(crate) mod test { // test store reloads with correct room state from the state store let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); - let joined_client = Client::new_with_config(homeserver, config).await.unwrap(); + let joined_client = Client::with_config(homeserver, config).await.unwrap(); joined_client.restore_login(session).await.unwrap(); // joined room reloaded from state store @@ -2696,7 +2696,7 @@ pub(crate) mod test { async fn login_error() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let config = ClientConfig::default().request_config(RequestConfig::new().disable_retry()); - let client = Client::new_with_config(homeserver, config).await.unwrap(); + let client = Client::with_config(homeserver, config).await.unwrap(); let _m = mock("POST", "/_matrix/client/r0/login") .with_status(403) @@ -3613,7 +3613,7 @@ pub(crate) mod test { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3)); assert!(config.request_config.retry_limit.unwrap() == 3); - let client = Client::new_with_config(homeserver, config).await.unwrap(); + let client = Client::with_config(homeserver, config).await.unwrap(); let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create(); @@ -3632,7 +3632,7 @@ pub(crate) mod test { let config = ClientConfig::default() .request_config(RequestConfig::new().retry_timeout(retry_timeout)); assert!(config.request_config.retry_timeout.unwrap() == retry_timeout); - let client = Client::new_with_config(homeserver, config).await.unwrap(); + let client = Client::with_config(homeserver, config).await.unwrap(); let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create(); @@ -3829,7 +3829,7 @@ pub(crate) mod test { .create(); let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3)); - let client = Client::new_with_config(homeserver.clone(), config).await.unwrap(); + let client = Client::with_config(homeserver.clone(), config).await.unwrap(); client.restore_login(session.clone()).await.unwrap(); let room = client.get_joined_room(room_id); From f5a7a1bcf067c95e735c10d13d0f3ffaa457a754 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:10:53 +0100 Subject: [PATCH 24/36] Rename AppService::{new_with_config => with_config} --- crates/matrix-sdk-appservice/src/lib.rs | 16 ++++++---------- crates/matrix-sdk-appservice/tests/tests.rs | 3 +-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index 6d990b3df..ef4b24fec 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -211,7 +211,7 @@ impl AppService { /// /// Also creates and caches a [`Client`] for the [`MainUser`]. /// The default [`ClientConfig`] is used, if you want to customize it - /// use [`Self::new_with_config()`] instead. + /// use [`Self::with_config()`] instead. /// /// # Arguments /// @@ -227,20 +227,16 @@ impl AppService { server_name: impl TryInto, Error = identifiers::Error>, registration: AppServiceRegistration, ) -> Result { - let appservice = Self::new_with_config( - homeserver_url, - server_name, - registration, - ClientConfig::default(), - ) - .await?; + let appservice = + Self::with_config(homeserver_url, server_name, registration, ClientConfig::default()) + .await?; Ok(appservice) } /// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the /// [`Client`] - pub async fn new_with_config( + pub async fn with_config( homeserver_url: impl TryInto, server_name: impl TryInto, Error = identifiers::Error>, registration: AppServiceRegistration, @@ -330,7 +326,7 @@ impl AppService { }; let client = - Client::new_with_config(self.homeserver_url.clone(), config.appservice_mode()).await?; + Client::with_config(self.homeserver_url.clone(), config.appservice_mode()).await?; let session = Session { access_token: self.registration.as_token.clone(), diff --git a/crates/matrix-sdk-appservice/tests/tests.rs b/crates/matrix-sdk-appservice/tests/tests.rs index c23f33e32..4092db182 100644 --- a/crates/matrix-sdk-appservice/tests/tests.rs +++ b/crates/matrix-sdk-appservice/tests/tests.rs @@ -35,8 +35,7 @@ async fn appservice(registration: Option) -> Result { let client_config = ClientConfig::default().request_config(RequestConfig::default().disable_retry()); - AppService::new_with_config(homeserver_url.as_ref(), server_name, registration, client_config) - .await + AppService::with_config(homeserver_url.as_ref(), server_name, registration, client_config).await } #[async_test] From cfdf4a032eb9d37234c56c290d6c8cec87a147dc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:13:29 +0100 Subject: [PATCH 25/36] Rename BaseClient::{new_with_store_config => with_store_config} --- crates/matrix-sdk-base/src/client.rs | 4 ++-- crates/matrix-sdk/src/client.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 5c2d5a8d9..375398f17 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -153,7 +153,7 @@ impl BaseClient { /// /// * `config` - An optional session if the user already has one from a /// previous login call. - pub fn new_with_store_config(config: StoreConfig) -> Self { + pub fn with_store_config(config: StoreConfig) -> Self { let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store); #[cfg(feature = "encryption")] let holder = config.crypto_store.map(CryptoHolder::new).unwrap_or_default(); @@ -171,7 +171,7 @@ impl BaseClient { impl BaseClient { /// Create a new default client. pub fn new() -> Self { - BaseClient::new_with_store_config(StoreConfig::default()) + BaseClient::with_store_config(StoreConfig::default()) } /// The current client session containing our user id, device id and access /// token. diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index 441e76a68..83fd8a0f4 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -183,7 +183,7 @@ impl Client { Arc::new(client_with_config(&config)?) }; - let base_client = BaseClient::new_with_store_config(config.store_config); + let base_client = BaseClient::with_store_config(config.store_config); let session = base_client.session().clone(); let http_client = From 1be68f57a53a9fb4a1ad9c1032948ba833651461 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:14:21 +0100 Subject: [PATCH 26/36] Merge BaseClient impl blocks --- crates/matrix-sdk-base/src/client.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 375398f17..148f57b5e 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -147,6 +147,11 @@ impl CryptoHolder { } impl BaseClient { + /// Create a new default client. + pub fn new() -> Self { + BaseClient::with_store_config(StoreConfig::default()) + } + /// Create a new client. /// /// # Arguments @@ -166,13 +171,7 @@ impl BaseClient { olm: Mutex::new(holder).into(), } } -} -impl BaseClient { - /// Create a new default client. - pub fn new() -> Self { - BaseClient::with_store_config(StoreConfig::default()) - } /// The current client session containing our user id, device id and access /// token. pub fn session(&self) -> &Arc>> { From 932ef6270ba8cf7c9d70d48fc352717918027218 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:30:55 +0100 Subject: [PATCH 27/36] Remove new_ prefix from OlmMachine ctors --- crates/matrix-sdk-base/src/client.rs | 2 +- crates/matrix-sdk-crypto/benches/crypto_bench.rs | 8 ++++---- crates/matrix-sdk-crypto/src/backups/mod.rs | 12 ++++-------- crates/matrix-sdk-crypto/src/machine.rs | 10 +++++----- crates/matrix-sdk-crypto/src/store/mod.rs | 2 +- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 148f57b5e..28dd160bd 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -123,7 +123,7 @@ impl CryptoHolder { async fn convert_to_olm(&mut self, session: &Session) -> Result<()> { if let CryptoHolder::PreSetupStore(store) = self { *self = CryptoHolder::Olm(Box::new( - OlmMachine::new_with_store( + OlmMachine::with_store( session.user_id.to_owned(), session.device_id.as_str().into(), store.take().expect("We always exist"), diff --git a/crates/matrix-sdk-crypto/benches/crypto_bench.rs b/crates/matrix-sdk-crypto/benches/crypto_bench.rs index 3e168d5e1..066f32ac4 100644 --- a/crates/matrix-sdk-crypto/benches/crypto_bench.rs +++ b/crates/matrix-sdk-crypto/benches/crypto_bench.rs @@ -73,7 +73,7 @@ pub fn keys_query(c: &mut Criterion) { let dir = tempfile::tempdir().unwrap(); let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap()); let machine = runtime - .block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store)) + .block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store)) .unwrap(); group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| { @@ -122,7 +122,7 @@ pub fn keys_claiming(c: &mut Criterion) { let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap()); let machine = runtime - .block_on(OlmMachine::new_with_store( + .block_on(OlmMachine::with_store( alice_id().into(), alice_device_id().into(), store, @@ -188,7 +188,7 @@ pub fn room_key_sharing(c: &mut Criterion) { let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap()); let machine = runtime - .block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store)) + .block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store)) .unwrap(); runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap(); runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap(); @@ -244,7 +244,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) { let store = Box::new(SledCryptoStore::open_with_passphrase(dir, None).unwrap()); let machine = runtime - .block_on(OlmMachine::new_with_store(alice_id().into(), alice_device_id().into(), store)) + .block_on(OlmMachine::with_store(alice_id().into(), alice_device_id().into(), store)) .unwrap(); runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap(); diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index c679df428..dc39d9681 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -470,13 +470,9 @@ mod test { use tempfile::tempdir; let tmpdir = tempdir().expect("Can't create a temporary dir"); - let machine = OlmMachine::new_with_default_store( - alice_id(), - alice_device_id(), - tmpdir.as_ref(), - None, - ) - .await?; + let machine = + OlmMachine::with_default_store(alice_id(), alice_device_id(), tmpdir.as_ref(), None) + .await?; backup_flow(machine).await } @@ -487,7 +483,7 @@ mod test { use tempfile::tempdir; let tmpdir = tempdir().expect("Can't create a temporary dir"); - let machine = OlmMachine::new_with_default_store( + let machine = OlmMachine::with_default_store( alice_id(), alice_device_id(), tmpdir.as_ref(), diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 613b92773..fff7eb7b0 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -220,7 +220,7 @@ impl OlmMachine { /// the encryption keys. /// /// [`Cryptostore`]: trait.CryptoStore.html - pub async fn new_with_store( + pub async fn with_store( user_id: Box, device_id: Box, store: Box, @@ -272,7 +272,7 @@ impl OlmMachine { /// /// * `device_id` - The unique id of the device that owns this machine. #[cfg(feature = "sled_cryptostore")] - pub async fn new_with_default_store( + pub async fn with_default_store( user_id: &UserId, device_id: &DeviceId, path: impl AsRef, @@ -280,7 +280,7 @@ impl OlmMachine { ) -> StoreResult { let store = SledStore::open_with_passphrase(path, passphrase)?; - OlmMachine::new_with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await + OlmMachine::with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await } /// The unique user id that owns this `OlmMachine` instance. @@ -2036,7 +2036,7 @@ pub(crate) mod test { let tmpdir = tempdir().unwrap(); - let machine = OlmMachine::new_with_default_store( + let machine = OlmMachine::with_default_store( user_id(), alice_device_id(), tmpdir.as_ref(), @@ -2053,7 +2053,7 @@ pub(crate) mod test { drop(machine); - let machine = OlmMachine::new_with_default_store( + let machine = OlmMachine::with_default_store( &user_id, alice_device_id(), tmpdir.as_ref(), diff --git a/crates/matrix-sdk-crypto/src/store/mod.rs b/crates/matrix-sdk-crypto/src/store/mod.rs index 4a4c39c8e..b01544ccf 100644 --- a/crates/matrix-sdk-crypto/src/store/mod.rs +++ b/crates/matrix-sdk-crypto/src/store/mod.rs @@ -31,7 +31,7 @@ //! # let device_id = device_id!("TEST").to_owned(); //! let store = Box::new(MemoryStore::new()); //! -//! let machine = OlmMachine::new_with_store(user_id, device_id, store); +//! let machine = OlmMachine::with_store(user_id, device_id, store); //! ``` //! //! [`OlmMachine`]: /matrix_sdk_crypto/struct.OlmMachine.html From 14ddcfb06eea5f5a1612fbdb595682891c8dbaba Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:31:37 +0100 Subject: [PATCH 28/36] Remove new_ prefix from ToDeviceRequest ctors --- crates/matrix-sdk-crypto/src/gossiping/mod.rs | 2 +- crates/matrix-sdk-crypto/src/requests.rs | 6 +++--- crates/matrix-sdk-crypto/src/verification/requests.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/gossiping/mod.rs b/crates/matrix-sdk-crypto/src/gossiping/mod.rs index a5f12b176..deff62615 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/mod.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/mod.rs @@ -150,7 +150,7 @@ impl GossipRequest { } }; - let request = ToDeviceRequest::new_with_id( + let request = ToDeviceRequest::with_id( &self.request_recipient, DeviceIdOrAllDevices::AllDevices, content, diff --git a/crates/matrix-sdk-crypto/src/requests.rs b/crates/matrix-sdk-crypto/src/requests.rs index 235ede122..7d9e90600 100644 --- a/crates/matrix-sdk-crypto/src/requests.rs +++ b/crates/matrix-sdk-crypto/src/requests.rs @@ -75,10 +75,10 @@ impl ToDeviceRequest { recipient_device: impl Into, content: AnyToDeviceEventContent, ) -> Self { - Self::new_with_id(recipient, recipient_device, content, TransactionId::new()) + Self::with_id(recipient, recipient_device, content, TransactionId::new()) } - pub(crate) fn new_for_recipients( + pub(crate) fn for_recipients( recipient: &UserId, recipient_devices: Vec>, content: AnyToDeviceEventContent, @@ -102,7 +102,7 @@ impl ToDeviceRequest { } } - pub(crate) fn new_with_id( + pub(crate) fn with_id( recipient: &UserId, recipient_device: impl Into, content: AnyToDeviceEventContent, diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index bf317ab6e..c5ca0fb67 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -164,7 +164,7 @@ impl VerificationRequest { milli_seconds_since_unix_epoch(), ); - ToDeviceRequest::new_for_recipients( + ToDeviceRequest::for_recipients( self.other_user(), self.recipient_devices.to_vec(), AnyToDeviceEventContent::KeyVerificationRequest(content), @@ -442,7 +442,7 @@ impl VerificationRequest { let request = content.map(|c| match c { OutgoingContent::ToDevice(content) => { if send_to_everyone { - ToDeviceRequest::new_for_recipients( + ToDeviceRequest::for_recipients( self.other_user(), self.recipient_devices.to_vec(), content, @@ -537,7 +537,7 @@ impl VerificationRequest { if recipients.is_empty() && filter_device.is_some() { None } else { - Some(ToDeviceRequest::new_for_recipients( + Some(ToDeviceRequest::for_recipients( self.other_user(), recipients, c, From 8a927df2b34ffc324cf5f943a0c261218e87e6f7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 12:32:40 +0100 Subject: [PATCH 29/36] Rename PrivateCrossSigningIdentity::{new_with_account => with_account} --- crates/matrix-sdk-crypto/src/identities/user.rs | 2 +- crates/matrix-sdk-crypto/src/olm/account.rs | 2 +- crates/matrix-sdk-crypto/src/olm/signing/mod.rs | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/user.rs b/crates/matrix-sdk-crypto/src/identities/user.rs index dc5fef90a..f20cbb910 100644 --- a/crates/matrix-sdk-crypto/src/identities/user.rs +++ b/crates/matrix-sdk-crypto/src/identities/user.rs @@ -1071,7 +1071,7 @@ pub(crate) mod test { let (_, device) = device(&response); let account = ReadOnlyAccount::new(device.user_id(), device.device_id()); - let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await; let id = Arc::new(Mutex::new(identity.clone())); diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 12ab17640..322eebe38 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -808,7 +808,7 @@ impl ReadOnlyAccount { pub async fn bootstrap_cross_signing( &self, ) -> (PrivateCrossSigningIdentity, UploadSigningKeysRequest, SignatureUploadRequest) { - PrivateCrossSigningIdentity::new_with_account(self).await + PrivateCrossSigningIdentity::with_account(self).await } /// Sign the given CrossSigning Key in place diff --git a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs index b33cfd02a..278d67f69 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs @@ -476,7 +476,7 @@ impl PrivateCrossSigningIdentity { /// * `account` - The Olm account that is creating the new identity. The /// account will sign the master key and the self signing key will sign the /// account. - pub(crate) async fn new_with_account( + pub(crate) async fn with_account( account: &ReadOnlyAccount, ) -> (Self, UploadSigningKeysRequest, SignatureUploadRequest) { let master = Signing::new(); @@ -739,7 +739,7 @@ mod test { #[async_test] async fn private_identity_signed_by_account() { let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID")); - let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await; let master = identity.master_key.lock().await; let master = master.as_ref().unwrap(); @@ -749,7 +749,7 @@ mod test { #[async_test] async fn sign_device() { let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID")); - let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await; let mut device = ReadOnlyDevice::from_account(&account).await; let self_signing = identity.self_signing_key.lock().await; @@ -766,10 +766,10 @@ mod test { #[async_test] async fn sign_user_identity() { let account = ReadOnlyAccount::new(user_id(), device_id!("DEVICEID")); - let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; + let (identity, _, _) = PrivateCrossSigningIdentity::with_account(&account).await; let bob_account = ReadOnlyAccount::new(user_id!("@bob:localhost"), device_id!("DEVICEID")); - let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await; + let (bob_private, _, _) = PrivateCrossSigningIdentity::with_account(&bob_account).await; let mut bob_public = ReadOnlyUserIdentity::from_private(&bob_private).await; let user_signing = identity.user_signing_key.lock().await; From 628e2fb716bb3a827d32a42cf897d5c7e9dfc638 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 15:31:11 +0100 Subject: [PATCH 30/36] Allow configuring assert_identity on the client level only Completely removing it from RequestConfig is left up to a future refactoring. --- crates/matrix-sdk-appservice/src/lib.rs | 11 ++++------- crates/matrix-sdk/src/config/client.rs | 13 +++++++++++++ crates/matrix-sdk/src/config/request.rs | 13 ------------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index ef4b24fec..e434a4cff 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -311,19 +311,16 @@ impl AppService { async fn create_and_cache_client( &self, localpart: &str, - config: ClientConfig, + mut config: ClientConfig, ) -> Result { let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?; // The `as_token` in the `Session` maps to the [`MainUser`] // (`sender_localpart`) by default, so we don't need to assert identity // in that case - let config = if localpart != self.registration.sender_localpart { - let request_config = config.get_request_config().assert_identity(); - config.request_config(request_config) - } else { - config - }; + if localpart != self.registration.sender_localpart { + config = config.assert_identity(); + } let client = Client::with_config(self.homeserver_url.clone(), config.appservice_mode()).await?; diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs index b4389b164..459fe08be 100644 --- a/crates/matrix-sdk/src/config/client.rs +++ b/crates/matrix-sdk/src/config/client.rs @@ -236,4 +236,17 @@ impl ClientConfig { self.use_discovery_response = true; self } + + /// All outgoing http requests will have a GET query key-value appended with + /// `user_id` being the key and the `user_id` from the `Session` being + /// the value. Will error if there's no `Session`. This is called + /// [identity assertion] in the Matrix Application Service Spec + /// + /// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion + #[cfg(feature = "appservice")] + #[must_use] + pub fn assert_identity(mut self) -> Self { + self.request_config.assert_identity = true; + self + } } diff --git a/crates/matrix-sdk/src/config/request.rs b/crates/matrix-sdk/src/config/request.rs index 28292316b..2d9c03686 100644 --- a/crates/matrix-sdk/src/config/request.rs +++ b/crates/matrix-sdk/src/config/request.rs @@ -121,17 +121,4 @@ impl RequestConfig { self.force_auth = true; self } - - /// All outgoing http requests will have a GET query key-value appended with - /// `user_id` being the key and the `user_id` from the `Session` being - /// the value. Will error if there's no `Session`. This is called - /// [identity assertion] in the Matrix Application Service Spec - /// - /// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion - #[cfg(feature = "appservice")] - #[must_use] - pub fn assert_identity(mut self) -> Self { - self.assert_identity = true; - self - } } From bd7fbea15565746869c66d495ea2085efcf10896 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 18:04:32 +0100 Subject: [PATCH 31/36] Fix intra-doc link --- crates/matrix-sdk/src/config/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/config/mod.rs b/crates/matrix-sdk/src/config/mod.rs index 6f759d643..39ade13e7 100644 --- a/crates/matrix-sdk/src/config/mod.rs +++ b/crates/matrix-sdk/src/config/mod.rs @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Configuration to change the behaviour of the [`Client`]. -//! -//! [`Client`]: #crate.Client +//! Configuration to change the behaviour of the [`Client`][crate::Client]. mod client; mod request; From d8c7fbd9dceeaf86d91ba835a6be97ca1652d27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 12 Mar 2022 13:30:55 +0100 Subject: [PATCH 32/36] sdk: Create Encryption struct Lower the API surface of Client --- .../examples/cross_signing_bootstrap.rs | 3 +- .../matrix-sdk/examples/emoji_verification.rs | 8 +- crates/matrix-sdk/src/client.rs | 8 + crates/matrix-sdk/src/docs/encryption.md | 2 +- .../src/encryption/identities/devices.rs | 14 +- .../src/encryption/identities/mod.rs | 4 +- .../src/encryption/identities/users.rs | 20 +- crates/matrix-sdk/src/encryption/mod.rs | 792 +++++++++--------- .../src/encryption/verification/sas.rs | 2 + crates/matrix-sdk/src/room/common.rs | 2 +- 10 files changed, 444 insertions(+), 411 deletions(-) diff --git a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs index bd82faba4..7f726675b 100644 --- a/crates/matrix-sdk/examples/cross_signing_bootstrap.rs +++ b/crates/matrix-sdk/examples/cross_signing_bootstrap.rs @@ -14,7 +14,7 @@ async fn bootstrap(client: Client, user_id: Box, password: String) { io::stdin().read_line(&mut input).expect("error: unable to read user input"); - if let Err(e) = client.bootstrap_cross_signing(None).await { + if let Err(e) = client.encryption().bootstrap_cross_signing(None).await { use matrix_sdk::ruma::{api::client::uiaa, assign}; if let Some(response) = e.uiaa_response() { @@ -27,6 +27,7 @@ async fn bootstrap(client: Client, user_id: Box, password: String) { )); client + .encryption() .bootstrap_cross_signing(Some(auth_data)) .await .expect("Couldn't bootstrap cross signing") diff --git a/crates/matrix-sdk/examples/emoji_verification.rs b/crates/matrix-sdk/examples/emoji_verification.rs index d7afc23cf..c8f25a312 100644 --- a/crates/matrix-sdk/examples/emoji_verification.rs +++ b/crates/matrix-sdk/examples/emoji_verification.rs @@ -54,7 +54,7 @@ fn print_result(sas: &SasVerification) { async fn print_devices(user_id: &UserId, client: &Client) { println!("Devices of user {}", user_id); - for device in client.get_user_devices(user_id).await.unwrap().devices() { + for device in client.encryption().get_user_devices(user_id).await.unwrap().devices() { println!( " {:<10} {:<30} {:<}", device.device_id(), @@ -87,6 +87,7 @@ async fn login( match event { AnyToDeviceEvent::KeyVerificationStart(e) => { if let Some(Verification::SasV1(sas)) = client + .encryption() .get_verification(&e.sender, e.content.transaction_id.as_str()) .await { @@ -102,6 +103,7 @@ async fn login( AnyToDeviceEvent::KeyVerificationKey(e) => { if let Some(Verification::SasV1(sas)) = client + .encryption() .get_verification(&e.sender, e.content.transaction_id.as_str()) .await { @@ -111,6 +113,7 @@ async fn login( AnyToDeviceEvent::KeyVerificationMac(e) => { if let Some(Verification::SasV1(sas)) = client + .encryption() .get_verification(&e.sender, e.content.transaction_id.as_str()) .await { @@ -136,6 +139,7 @@ async fn login( if let MessageType::VerificationRequest(_) = &m.content.msgtype { let request = client + .encryption() .get_verification_request(&m.sender, &m.event_id) .await .expect("Request object wasn't created"); @@ -148,6 +152,7 @@ async fn login( } AnySyncMessageEvent::KeyVerificationKey(e) => { if let Some(Verification::SasV1(sas)) = client + .encryption() .get_verification( &e.sender, e.content.relates_to.event_id.as_str(), @@ -159,6 +164,7 @@ async fn login( } AnySyncMessageEvent::KeyVerificationMac(e) => { if let Some(Verification::SasV1(sas)) = client + .encryption() .get_verification( &e.sender, e.content.relates_to.event_id.as_str(), diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client.rs index 83fd8a0f4..68ca42344 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client.rs @@ -65,6 +65,8 @@ use serde::de::DeserializeOwned; use tracing::{error, info, instrument, warn}; use url::Url; +#[cfg(feature = "encryption")] +use crate::encryption::Encryption; use crate::{ attachment::{AttachmentInfo, Thumbnail}, config::{ClientConfig, RequestConfig}, @@ -429,6 +431,12 @@ impl Client { Account::new(self.clone()) } + /// Get the encryption manager of the client. + #[cfg(feature = "encryption")] + pub fn encryption(&self) -> Encryption { + Encryption::new(self.clone()) + } + /// Register a handler for a specific event type. /// /// The handler is a function or closure with one or more arguments. The diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 5f6c2db07..ff20ff43e 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -87,7 +87,7 @@ stored, otherwise we won't be able to decrypt historical messages. The SDK stores all room keys locally in a encrypted manner. Besides storing them as part of the SDK store, users can export room keys -using the [`Client::export_keys`] method. +using the [`Encryption::export_keys`] method. # Verification diff --git a/crates/matrix-sdk/src/encryption/identities/devices.rs b/crates/matrix-sdk/src/encryption/identities/devices.rs index 208279566..6086d4f7c 100644 --- a/crates/matrix-sdk/src/encryption/identities/devices.rs +++ b/crates/matrix-sdk/src/encryption/identities/devices.rs @@ -89,7 +89,7 @@ impl Device { /// # let alice = user_id!("@alice:example.org"); /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; - /// let device = client.get_device(alice, device_id!("DEVICEID")).await?; + /// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; /// /// if let Some(device) = device { /// let verification = device.request_verification().await?; @@ -137,7 +137,7 @@ impl Device { /// # let alice = user_id!("@alice:example.org"); /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; - /// let device = client.get_device(alice, device_id!("DEVICEID")).await?; + /// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; /// /// // We don't want to support showing a QR code, we only support SAS /// // verification @@ -179,7 +179,7 @@ impl Device { /// # let alice = user_id!("@alice:example.org"); /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; - /// let device = client.get_device(alice, device_id!("DEVICEID")).await?; + /// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; /// /// if let Some(device) = device { /// let verification = device.start_verification().await?; @@ -212,9 +212,9 @@ impl Device { /// key. /// /// The state of our private cross signing keys can be inspected using the - /// [`Client::cross_signing_status()`] method. + /// [`Encryption::cross_signing_status()`] method. /// - /// [`Client::cross_signing_status()`]: crate::Client::cross_signing_status + /// [`Encryption::cross_signing_status()`]: crate::encryption::Encryption::cross_signing_status /// /// ### Problems of manual verification /// @@ -243,7 +243,7 @@ impl Device { /// # let alice = user_id!("@alice:example.org"); /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; - /// let device = client.get_device(alice, device_id!("DEVICEID")).await?; + /// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; /// /// if let Some(device) = device { /// device.verify().await?; @@ -358,7 +358,7 @@ impl Device { /// # let alice = user_id!("@alice:example.org"); /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; - /// let device = client.get_device(alice, device_id!("DEVICEID")).await?; + /// let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; /// /// if let Some(device) = device { /// if device.verified() { diff --git a/crates/matrix-sdk/src/encryption/identities/mod.rs b/crates/matrix-sdk/src/encryption/identities/mod.rs index e0beea868..3f95580f5 100644 --- a/crates/matrix-sdk/src/encryption/identities/mod.rs +++ b/crates/matrix-sdk/src/encryption/identities/mod.rs @@ -43,7 +43,7 @@ //! # let homeserver = Url::parse("http://example.com").unwrap(); //! # block_on(async { //! # let client = Client::new(homeserver).await.unwrap(); -//! let device = client.get_device(alice, device_id!("DEVICEID")).await?; +//! let device = client.encryption().get_device(alice, device_id!("DEVICEID")).await?; //! //! if let Some(device) = device { //! // Let's request the device to be verified. @@ -69,7 +69,7 @@ //! # let homeserver = Url::parse("http://example.com").unwrap(); //! # block_on(async { //! # let client = Client::new(homeserver).await.unwrap(); -//! let user = client.get_user_identity(alice).await?; +//! let user = client.encryption().get_user_identity(alice).await?; //! //! if let Some(user) = user { //! // Let's request the user to be verified. diff --git a/crates/matrix-sdk/src/encryption/identities/users.rs b/crates/matrix-sdk/src/encryption/identities/users.rs index b7424f6ca..c1d8bd42b 100644 --- a/crates/matrix-sdk/src/encryption/identities/users.rs +++ b/crates/matrix-sdk/src/encryption/identities/users.rs @@ -35,8 +35,8 @@ use crate::{encryption::verification::VerificationRequest, room::Joined, Client} /// /// The identity is backed by public [cross signing] keys that users upload. If /// our own user doesn't yet have such an identity, a new one can be created and -/// uploaded to the server using [`Client::bootstrap_cross_signing()`]. The user -/// identity can be also reset using the same method. +/// uploaded to the server using [`Encryption::bootstrap_cross_signing()`]. The +/// user identity can be also reset using the same method. /// /// The user identity consists of three separate `Ed25519` keypairs: /// @@ -63,6 +63,7 @@ use crate::{encryption::verification::VerificationRequest, room::Joined, Client} /// let us know whom the user verified. /// /// [cross signing]: https://spec.matrix.org/unstable/client-server-api/#cross-signing +/// [`Encryption::bootstrap_cross_signing()`]: crate::encryption::Encryption::bootstrap_cross_signing #[derive(Debug, Clone)] pub struct UserIdentity { inner: UserIdentities, @@ -97,7 +98,7 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # futures::executor::block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// if let Some(user) = user { /// println!("This user identity belongs to {}", user.user_id().as_str()); @@ -149,7 +150,7 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # futures::executor::block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// if let Some(user) = user { /// let verification = user.request_verification().await?; @@ -208,7 +209,7 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// // We don't want to support showing a QR code, we only support SAS /// // verification @@ -252,7 +253,7 @@ impl UserIdentity { /// course fail if the private part of the User-signing key isn't available. /// /// The availability of the User-signing key can be checked using the - /// [`Client::cross_signing_status()`] method. + /// [`Encryption::cross_signing_status()`] method. /// /// ### Manually verifying our own user /// @@ -287,13 +288,14 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// if let Some(user) = user { /// user.verify().await?; /// } /// # anyhow::Result::<()>::Ok(()) }); /// ``` + /// [`Encryption::cross_signing_status()`]: crate::encryption::Encryption::cross_signing_status pub async fn verify(&self) -> Result<(), ManualVerifyError> { match &self.inner { UserIdentities::Own(i) => i.verify().await, @@ -330,7 +332,7 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// if let Some(user) = user { /// if user.verified() { @@ -370,7 +372,7 @@ impl UserIdentity { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # block_on(async { /// # let client = Client::new(homeserver).await.unwrap(); - /// let user = client.get_user_identity(alice).await?; + /// let user = client.encryption().get_user_identity(alice).await?; /// /// if let Some(user) = user { /// // Let's verify the user after we confirm that the master key diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 58f7a54be..2f5810744 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -65,395 +65,6 @@ use crate::{ }; impl Client { - /// Get the public ed25519 key of our own device. This is usually what is - /// called the fingerprint of the device. - #[cfg(feature = "encryption")] - pub async fn ed25519_key(&self) -> Option { - self.olm_machine().await.map(|o| o.identity_keys().ed25519().to_owned()) - } - - /// Get the status of the private cross signing keys. - /// - /// This can be used to check which private cross signing keys we have - /// stored locally. - #[cfg(feature = "encryption")] - pub async fn cross_signing_status(&self) -> Option { - if let Some(machine) = self.olm_machine().await { - Some(machine.cross_signing_status().await) - } else { - None - } - } - - /// Get all the tracked users we know about - /// - /// Tracked users are users for which we keep the device list of E2EE - /// capable devices up to date. - #[cfg(feature = "encryption")] - pub async fn tracked_users(&self) -> HashSet> { - self.olm_machine().await.map(|o| o.tracked_users()).unwrap_or_default() - } - - /// Get a verification object with the given flow id. - #[cfg(feature = "encryption")] - pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option { - let olm = self.olm_machine().await?; - olm.get_verification(user_id, flow_id).map(|v| match v { - matrix_sdk_base::crypto::Verification::SasV1(s) => { - SasVerification { inner: s, client: self.clone() }.into() - } - #[cfg(feature = "qrcode")] - matrix_sdk_base::crypto::Verification::QrV1(qr) => { - verification::QrVerification { inner: qr, client: self.clone() }.into() - } - }) - } - - /// Get a `VerificationRequest` object for the given user with the given - /// flow id. - #[cfg(feature = "encryption")] - pub async fn get_verification_request( - &self, - user_id: &UserId, - flow_id: impl AsRef, - ) -> Option { - let olm = self.olm_machine().await?; - - olm.get_verification_request(user_id, flow_id) - .map(|r| VerificationRequest { inner: r, client: self.clone() }) - } - - /// Get a specific device of a user. - /// - /// # Arguments - /// - /// * `user_id` - The unique id of the user that the device belongs to. - /// - /// * `device_id` - The unique id of the device. - /// - /// Returns a `Device` if one is found and the crypto store didn't throw an - /// error. - /// - /// This will always return None if the client hasn't been logged in. - /// - /// # Example - /// - /// ```no_run - /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, ruma::{device_id, user_id}}; - /// # use url::Url; - /// # use futures::executor::block_on; - /// # block_on(async { - /// # let alice = user_id!("@alice:example.org"); - /// # let homeserver = Url::parse("http://example.com")?; - /// # let client = Client::new(homeserver).await?; - /// if let Some(device) = client.get_device(alice, device_id!("DEVICEID")).await? { - /// println!("{:?}", device.verified()); - /// - /// if !device.verified() { - /// let verification = device.request_verification().await?; - /// } - /// } - /// # anyhow::Result::<()>::Ok(()) }); - /// ``` - #[cfg(feature = "encryption")] - pub async fn get_device( - &self, - user_id: &UserId, - device_id: &DeviceId, - ) -> Result, CryptoStoreError> { - let device = self.base_client().get_device(user_id, device_id).await?; - - Ok(device.map(|d| Device { inner: d, client: self.clone() })) - } - - /// Get a map holding all the devices of an user. - /// - /// This will always return an empty map if the client hasn't been logged - /// in. - /// - /// # Arguments - /// - /// * `user_id` - The unique id of the user that the devices belong to. - /// - /// # Example - /// - /// ```no_run - /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, ruma::user_id}; - /// # use url::Url; - /// # use futures::executor::block_on; - /// # block_on(async { - /// # let alice = user_id!("@alice:example.org"); - /// # let homeserver = Url::parse("http://example.com")?; - /// # let client = Client::new(homeserver).await?; - /// let devices = client.get_user_devices(alice).await?; - /// - /// for device in devices.devices() { - /// println!("{:?}", device); - /// } - /// # anyhow::Result::<()>::Ok(()) }); - /// ``` - #[cfg(feature = "encryption")] - pub async fn get_user_devices( - &self, - user_id: &UserId, - ) -> Result { - let devices = self.base_client().get_user_devices(user_id).await?; - - Ok(UserDevices { inner: devices, client: self.clone() }) - } - - /// Get a E2EE identity of an user. - /// - /// # Arguments - /// - /// * `user_id` - The unique id of the user that the identity belongs to. - /// - /// Returns a `UserIdentity` if one is found and the crypto store - /// didn't throw an error. - /// - /// This will always return None if the client hasn't been logged in. - /// - /// # Example - /// - /// ```no_run - /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, ruma::user_id}; - /// # use url::Url; - /// # use futures::executor::block_on; - /// # block_on(async { - /// # let alice = user_id!("@alice:example.org"); - /// # let homeserver = Url::parse("http://example.com")?; - /// # let client = Client::new(homeserver).await?; - /// let user = client.get_user_identity(alice).await?; - /// - /// if let Some(user) = user { - /// println!("{:?}", user.verified()); - /// - /// let verification = user.request_verification().await?; - /// } - /// # anyhow::Result::<()>::Ok(()) }); - /// ``` - #[cfg(feature = "encryption")] - pub async fn get_user_identity( - &self, - user_id: &UserId, - ) -> Result, CryptoStoreError> { - use crate::encryption::identities::UserIdentity; - - if let Some(olm) = self.olm_machine().await { - let identity = olm.get_identity(user_id).await?; - - Ok(identity.map(|i| match i { - matrix_sdk_base::crypto::UserIdentities::Own(i) => { - UserIdentity::new_own(self.clone(), i) - } - matrix_sdk_base::crypto::UserIdentities::Other(i) => { - UserIdentity::new(self.clone(), i, self.get_dm_room(user_id)) - } - })) - } else { - Ok(None) - } - } - - /// Create and upload a new cross signing identity. - /// - /// # Arguments - /// - /// * `auth_data` - This request requires user interactive auth, the first - /// request needs to set this to `None` and will always fail with an - /// `UiaaResponse`. The response will contain information for the - /// interactive auth and the same request needs to be made but this time - /// with some `auth_data` provided. - /// - /// # Examples - /// ```no_run - /// # use std::{convert::TryFrom, collections::BTreeMap}; - /// # use matrix_sdk::{ - /// # ruma::{api::client::uiaa, assign}, - /// # Client, - /// # }; - /// # use url::Url; - /// # use futures::executor::block_on; - /// # use serde_json::json; - /// # block_on(async { - /// # let homeserver = Url::parse("http://example.com")?; - /// # let client = Client::new(homeserver).await?; - /// if let Err(e) = client.bootstrap_cross_signing(None).await { - /// if let Some(response) = e.uiaa_response() { - /// let auth_data = uiaa::AuthData::Password(assign!( - /// uiaa::Password::new( - /// uiaa::UserIdentifier::UserIdOrLocalpart("example"), - /// "wordpass", - /// ), { - /// session: response.session.as_deref(), - /// } - /// )); - /// - /// client - /// .bootstrap_cross_signing(Some(auth_data)) - /// .await - /// .expect("Couldn't bootstrap cross signing") - /// } else { - /// panic!("Error durign cross signing bootstrap {:#?}", e); - /// } - /// } - /// # anyhow::Result::<()>::Ok(()) }); - #[cfg(feature = "encryption")] - pub async fn bootstrap_cross_signing(&self, auth_data: Option>) -> Result<()> { - let olm = self.olm_machine().await.ok_or(Error::AuthenticationRequired)?; - - let (request, signature_request) = olm.bootstrap_cross_signing(false).await?; - - let to_raw = |k| Raw::new(&k).expect("Can't serialize newly created cross signing keys"); - - let request = assign!(UploadSigningKeysRequest::new(), { - auth: auth_data, - master_key: request.master_key.map(to_raw), - self_signing_key: request.self_signing_key.map(to_raw), - user_signing_key: request.user_signing_key.map(to_raw), - }); - - self.send(request, None).await?; - self.send(signature_request, None).await?; - - Ok(()) - } - - /// Export E2EE keys that match the given predicate encrypting them with the - /// given passphrase. - /// - /// # Arguments - /// - /// * `path` - The file path where the exported key file will be saved. - /// - /// * `passphrase` - The passphrase that will be used to encrypt the - /// exported - /// room keys. - /// - /// * `predicate` - A closure that will be called for every known - /// `InboundGroupSession`, which represents a room key. If the closure - /// returns `true` the `InboundGroupSessoin` will be included in the export, - /// if the closure returns `false` it will not be included. - /// - /// # Panics - /// - /// This method will panic if it isn't run on a Tokio runtime. - /// - /// This method will panic if it can't get enough randomness from the OS to - /// encrypt the exported keys securely. - /// - /// # Examples - /// - /// ```no_run - /// # use std::{path::PathBuf, time::Duration}; - /// # use matrix_sdk::{ - /// # Client, config::SyncSettings, - /// # ruma::room_id, - /// # }; - /// # use futures::executor::block_on; - /// # use url::Url; - /// # block_on(async { - /// # let homeserver = Url::parse("http://localhost:8080")?; - /// # let mut client = Client::new(homeserver).await?; - /// let path = PathBuf::from("/home/example/e2e-keys.txt"); - /// // Export all room keys. - /// client - /// .export_keys(path, "secret-passphrase", |_| true) - /// .await?; - /// - /// // Export only the room keys for a certain room. - /// let path = PathBuf::from("/home/example/e2e-room-keys.txt"); - /// let room_id = room_id!("!test:localhost"); - /// - /// client - /// .export_keys(path, "secret-passphrase", |s| s.room_id() == room_id) - /// .await?; - /// # anyhow::Result::<()>::Ok(()) }); - /// ``` - #[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] - pub async fn export_keys( - &self, - path: PathBuf, - passphrase: &str, - predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool, - ) -> Result<()> { - let olm = self.olm_machine().await.ok_or(Error::AuthenticationRequired)?; - - let keys = olm.export_keys(predicate).await?; - let passphrase = zeroize::Zeroizing::new(passphrase.to_owned()); - - let encrypt = move || -> Result<()> { - let export: String = - matrix_sdk_base::crypto::encrypt_key_export(&keys, &passphrase, 500_000)?; - let mut file = std::fs::File::create(path)?; - file.write_all(&export.into_bytes())?; - Ok(()) - }; - - let task = tokio::task::spawn_blocking(encrypt); - task.await.expect("Task join error") - } - - /// Import E2EE keys from the given file path. - /// - /// # Arguments - /// - /// * `path` - The file path where the exported key file will can be found. - /// - /// * `passphrase` - The passphrase that should be used to decrypt the - /// exported room keys. - /// - /// Returns a tuple of numbers that represent the number of sessions that - /// were imported and the total number of sessions that were found in the - /// key export. - /// - /// # Panics - /// - /// This method will panic if it isn't run on a Tokio runtime. - /// - /// ```no_run - /// # use std::{path::PathBuf, time::Duration}; - /// # use matrix_sdk::{ - /// # Client, config::SyncSettings, - /// # ruma::room_id, - /// # }; - /// # use futures::executor::block_on; - /// # use url::Url; - /// # block_on(async { - /// # let homeserver = Url::parse("http://localhost:8080")?; - /// # let mut client = Client::new(homeserver).await?; - /// let path = PathBuf::from("/home/example/e2e-keys.txt"); - /// let result = client.import_keys(path, "secret-passphrase").await?; - /// - /// println!( - /// "Imported {} room keys out of {}", - /// result.imported_count, result.total_count - /// ); - /// # anyhow::Result::<()>::Ok(()) }); - /// ``` - #[cfg(all(feature = "encryption", not(target_arch = "wasm32")))] - pub async fn import_keys( - &self, - path: PathBuf, - passphrase: &str, - ) -> Result { - let olm = self.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?; - let passphrase = zeroize::Zeroizing::new(passphrase.to_owned()); - - let decrypt = move || { - let file = std::fs::File::open(path)?; - matrix_sdk_base::crypto::decrypt_key_export(file, &passphrase) - }; - - let task = tokio::task::spawn_blocking(decrypt); - let import = task.await.expect("Task join error")?; - - Ok(olm.import_keys(import, false, |_, _| {}).await?) - } - /// Tries to decrypt a `AnyRoomEvent`. Returns undecrypted room event when /// decryption fails. #[cfg(feature = "encryption")] @@ -877,3 +488,406 @@ impl Client { Ok(()) } } + +/// A high-level API to manage the client's encryption. +/// +/// To get this, use [`Client::encryption()`]. +#[cfg(feature = "encryption")] +#[derive(Debug, Clone)] +pub struct Encryption { + /// The underlying client. + client: Client, +} + +#[cfg(feature = "encryption")] +impl Encryption { + pub(crate) fn new(client: Client) -> Self { + Self { client } + } + + /// Get the public ed25519 key of our own device. This is usually what is + /// called the fingerprint of the device. + pub async fn ed25519_key(&self) -> Option { + self.client.olm_machine().await.map(|o| o.identity_keys().ed25519().to_owned()) + } + + /// Get the status of the private cross signing keys. + /// + /// This can be used to check which private cross signing keys we have + /// stored locally. + pub async fn cross_signing_status(&self) -> Option { + if let Some(machine) = self.client.olm_machine().await { + Some(machine.cross_signing_status().await) + } else { + None + } + } + + /// Get all the tracked users we know about + /// + /// Tracked users are users for which we keep the device list of E2EE + /// capable devices up to date. + pub async fn tracked_users(&self) -> HashSet> { + self.client.olm_machine().await.map(|o| o.tracked_users()).unwrap_or_default() + } + + /// Get a verification object with the given flow id. + pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option { + let olm = self.client.olm_machine().await?; + olm.get_verification(user_id, flow_id).map(|v| match v { + matrix_sdk_base::crypto::Verification::SasV1(s) => { + SasVerification { inner: s, client: self.client.clone() }.into() + } + #[cfg(feature = "qrcode")] + matrix_sdk_base::crypto::Verification::QrV1(qr) => { + verification::QrVerification { inner: qr, client: self.client.clone() }.into() + } + }) + } + + /// Get a `VerificationRequest` object for the given user with the given + /// flow id. + pub async fn get_verification_request( + &self, + user_id: &UserId, + flow_id: impl AsRef, + ) -> Option { + let olm = self.client.olm_machine().await?; + + olm.get_verification_request(user_id, flow_id) + .map(|r| VerificationRequest { inner: r, client: self.client.clone() }) + } + + /// Get a specific device of a user. + /// + /// # Arguments + /// + /// * `user_id` - The unique id of the user that the device belongs to. + /// + /// * `device_id` - The unique id of the device. + /// + /// Returns a `Device` if one is found and the crypto store didn't throw an + /// error. + /// + /// This will always return None if the client hasn't been logged in. + /// + /// # Example + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::{device_id, user_id}}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # block_on(async { + /// # let alice = user_id!("@alice:example.org"); + /// # let homeserver = Url::parse("http://example.com")?; + /// # let client = Client::new(homeserver).await?; + /// if let Some(device) = client + /// .encryption() + /// .get_device(alice, device_id!("DEVICEID")) + /// .await? { + /// println!("{:?}", device.verified()); + /// + /// if !device.verified() { + /// let verification = device.request_verification().await?; + /// } + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub async fn get_device( + &self, + user_id: &UserId, + device_id: &DeviceId, + ) -> Result, CryptoStoreError> { + let device = self.client.base_client().get_device(user_id, device_id).await?; + + Ok(device.map(|d| Device { inner: d, client: self.client.clone() })) + } + + /// Get a map holding all the devices of an user. + /// + /// This will always return an empty map if the client hasn't been logged + /// in. + /// + /// # Arguments + /// + /// * `user_id` - The unique id of the user that the devices belong to. + /// + /// # Example + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::user_id}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # block_on(async { + /// # let alice = user_id!("@alice:example.org"); + /// # let homeserver = Url::parse("http://example.com")?; + /// # let client = Client::new(homeserver).await?; + /// let devices = client.encryption().get_user_devices(alice).await?; + /// + /// for device in devices.devices() { + /// println!("{:?}", device); + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub async fn get_user_devices( + &self, + user_id: &UserId, + ) -> Result { + let devices = self.client.base_client().get_user_devices(user_id).await?; + + Ok(UserDevices { inner: devices, client: self.client.clone() }) + } + + /// Get a E2EE identity of an user. + /// + /// # Arguments + /// + /// * `user_id` - The unique id of the user that the identity belongs to. + /// + /// Returns a `UserIdentity` if one is found and the crypto store + /// didn't throw an error. + /// + /// This will always return None if the client hasn't been logged in. + /// + /// # Example + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::user_id}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # block_on(async { + /// # let alice = user_id!("@alice:example.org"); + /// # let homeserver = Url::parse("http://example.com")?; + /// # let client = Client::new(homeserver).await?; + /// let user = client.encryption().get_user_identity(alice).await?; + /// + /// if let Some(user) = user { + /// println!("{:?}", user.verified()); + /// + /// let verification = user.request_verification().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub async fn get_user_identity( + &self, + user_id: &UserId, + ) -> Result, CryptoStoreError> { + use crate::encryption::identities::UserIdentity; + + if let Some(olm) = self.client.olm_machine().await { + let identity = olm.get_identity(user_id).await?; + + Ok(identity.map(|i| match i { + matrix_sdk_base::crypto::UserIdentities::Own(i) => { + UserIdentity::new_own(self.client.clone(), i) + } + matrix_sdk_base::crypto::UserIdentities::Other(i) => { + UserIdentity::new(self.client.clone(), i, self.client.get_dm_room(user_id)) + } + })) + } else { + Ok(None) + } + } + + /// Create and upload a new cross signing identity. + /// + /// # Arguments + /// + /// * `auth_data` - This request requires user interactive auth, the first + /// request needs to set this to `None` and will always fail with an + /// `UiaaResponse`. The response will contain information for the + /// interactive auth and the same request needs to be made but this time + /// with some `auth_data` provided. + /// + /// # Examples + /// ```no_run + /// # use std::{convert::TryFrom, collections::BTreeMap}; + /// # use matrix_sdk::{ + /// # ruma::{api::client::uiaa, assign}, + /// # Client, + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # use serde_json::json; + /// # block_on(async { + /// # let homeserver = Url::parse("http://example.com")?; + /// # let client = Client::new(homeserver).await?; + /// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await { + /// if let Some(response) = e.uiaa_response() { + /// let auth_data = uiaa::AuthData::Password(assign!( + /// uiaa::Password::new( + /// uiaa::UserIdentifier::UserIdOrLocalpart("example"), + /// "wordpass", + /// ), { + /// session: response.session.as_deref(), + /// } + /// )); + /// + /// client + /// .encryption() + /// .bootstrap_cross_signing(Some(auth_data)) + /// .await + /// .expect("Couldn't bootstrap cross signing") + /// } else { + /// panic!("Error durign cross signing bootstrap {:#?}", e); + /// } + /// } + /// # anyhow::Result::<()>::Ok(()) }); + pub async fn bootstrap_cross_signing(&self, auth_data: Option>) -> Result<()> { + let olm = self.client.olm_machine().await.ok_or(Error::AuthenticationRequired)?; + + let (request, signature_request) = olm.bootstrap_cross_signing(false).await?; + + let to_raw = |k| Raw::new(&k).expect("Can't serialize newly created cross signing keys"); + + let request = assign!(UploadSigningKeysRequest::new(), { + auth: auth_data, + master_key: request.master_key.map(to_raw), + self_signing_key: request.self_signing_key.map(to_raw), + user_signing_key: request.user_signing_key.map(to_raw), + }); + + self.client.send(request, None).await?; + self.client.send(signature_request, None).await?; + + Ok(()) + } + + /// Export E2EE keys that match the given predicate encrypting them with the + /// given passphrase. + /// + /// # Arguments + /// + /// * `path` - The file path where the exported key file will be saved. + /// + /// * `passphrase` - The passphrase that will be used to encrypt the + /// exported + /// room keys. + /// + /// * `predicate` - A closure that will be called for every known + /// `InboundGroupSession`, which represents a room key. If the closure + /// returns `true` the `InboundGroupSessoin` will be included in the export, + /// if the closure returns `false` it will not be included. + /// + /// # Panics + /// + /// This method will panic if it isn't run on a Tokio runtime. + /// + /// This method will panic if it can't get enough randomness from the OS to + /// encrypt the exported keys securely. + /// + /// # Examples + /// + /// ```no_run + /// # use std::{path::PathBuf, time::Duration}; + /// # use matrix_sdk::{ + /// # Client, config::SyncSettings, + /// # ruma::room_id, + /// # }; + /// # use futures::executor::block_on; + /// # use url::Url; + /// # block_on(async { + /// # let homeserver = Url::parse("http://localhost:8080")?; + /// # let mut client = Client::new(homeserver).await?; + /// let path = PathBuf::from("/home/example/e2e-keys.txt"); + /// // Export all room keys. + /// client + /// .encryption() + /// .export_keys(path, "secret-passphrase", |_| true) + /// .await?; + /// + /// // Export only the room keys for a certain room. + /// let path = PathBuf::from("/home/example/e2e-room-keys.txt"); + /// let room_id = room_id!("!test:localhost"); + /// + /// client + /// .encryption() + /// .export_keys(path, "secret-passphrase", |s| s.room_id() == room_id) + /// .await?; + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + #[cfg(not(target_arch = "wasm32"))] + pub async fn export_keys( + &self, + path: PathBuf, + passphrase: &str, + predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool, + ) -> Result<()> { + let olm = self.client.olm_machine().await.ok_or(Error::AuthenticationRequired)?; + + let keys = olm.export_keys(predicate).await?; + let passphrase = zeroize::Zeroizing::new(passphrase.to_owned()); + + let encrypt = move || -> Result<()> { + let export: String = + matrix_sdk_base::crypto::encrypt_key_export(&keys, &passphrase, 500_000)?; + let mut file = std::fs::File::create(path)?; + file.write_all(&export.into_bytes())?; + Ok(()) + }; + + let task = tokio::task::spawn_blocking(encrypt); + task.await.expect("Task join error") + } + + /// Import E2EE keys from the given file path. + /// + /// # Arguments + /// + /// * `path` - The file path where the exported key file will can be found. + /// + /// * `passphrase` - The passphrase that should be used to decrypt the + /// exported room keys. + /// + /// Returns a tuple of numbers that represent the number of sessions that + /// were imported and the total number of sessions that were found in the + /// key export. + /// + /// # Panics + /// + /// This method will panic if it isn't run on a Tokio runtime. + /// + /// ```no_run + /// # use std::{path::PathBuf, time::Duration}; + /// # use matrix_sdk::{ + /// # Client, config::SyncSettings, + /// # ruma::room_id, + /// # }; + /// # use futures::executor::block_on; + /// # use url::Url; + /// # block_on(async { + /// # let homeserver = Url::parse("http://localhost:8080")?; + /// # let mut client = Client::new(homeserver).await?; + /// let path = PathBuf::from("/home/example/e2e-keys.txt"); + /// let result = client.encryption().import_keys(path, "secret-passphrase").await?; + /// + /// println!( + /// "Imported {} room keys out of {}", + /// result.imported_count, result.total_count + /// ); + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + #[cfg(not(target_arch = "wasm32"))] + pub async fn import_keys( + &self, + path: PathBuf, + passphrase: &str, + ) -> Result { + let olm = self.client.olm_machine().await.ok_or(RoomKeyImportError::StoreClosed)?; + let passphrase = zeroize::Zeroizing::new(passphrase.to_owned()); + + let decrypt = move || { + let file = std::fs::File::open(path)?; + matrix_sdk_base::crypto::decrypt_key_export(file, &passphrase) + }; + + let task = tokio::task::spawn_blocking(decrypt); + let import = task.await.expect("Task join error")?; + + Ok(olm.import_keys(import, false, |_, _| {}).await?) + } +} diff --git a/crates/matrix-sdk/src/encryption/verification/sas.rs b/crates/matrix-sdk/src/encryption/verification/sas.rs index c2cb6e9f9..a5ee0bf16 100644 --- a/crates/matrix-sdk/src/encryption/verification/sas.rs +++ b/crates/matrix-sdk/src/encryption/verification/sas.rs @@ -54,6 +54,7 @@ impl SasVerification { /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; /// let sas = client + /// .encryption() /// .get_verification(&user_id, flow_id) /// .await /// .and_then(|v| v.sas()); @@ -128,6 +129,7 @@ impl SasVerification { /// # let homeserver = Url::parse("http://example.com")?; /// # let client = Client::new(homeserver).await?; /// let sas_verification = client + /// .encryption() /// .get_verification(&user_id, flow_id) /// .await /// .and_then(|v| v.sas()); diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index b0218114d..4d20437ff 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -667,7 +667,7 @@ impl Common { let user_ids = self.client.store().get_user_ids(self.room_id()).await?; for user_id in user_ids { - let devices = self.client.get_user_devices(&user_id).await?; + let devices = self.client.encryption().get_user_devices(&user_id).await?; let any_unverified = devices.devices().any(|d| !d.verified()); if any_unverified { From 147e948fe6ef0d6f762cd8ede0dc7282a532103d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 18:29:43 +0100 Subject: [PATCH 33/36] Replace ClientConfig with ClientBuilder This includes a few not-strictly-related changes that made sense to do at the same time: * Check for a functioning homeserver via get_supported_versions regardless of whether a homeserver URL or user ID was supplied * Rename use_discovery_response to respect_login_well_known * Small appservice documentation improvement because those docs had to be touched anyways * Some test refactorings in tests that had to be touched anyways --- crates/matrix-sdk-appservice/src/lib.rs | 55 +-- crates/matrix-sdk-appservice/tests/tests.rs | 16 +- crates/matrix-sdk/examples/autojoin.rs | 23 +- crates/matrix-sdk/examples/command_bot.rs | 22 +- crates/matrix-sdk/src/client/builder.rs | 401 ++++++++++++++++++ .../src/{client.rs => client/mod.rs} | 236 ++++------- crates/matrix-sdk/src/config/client.rs | 252 ----------- crates/matrix-sdk/src/config/mod.rs | 2 - crates/matrix-sdk/src/config/request.rs | 2 +- crates/matrix-sdk/src/docs/encryption.md | 4 +- crates/matrix-sdk/src/http_client.rs | 83 ++-- crates/matrix-sdk/src/lib.rs | 2 +- crates/matrix-sdk/src/store.rs | 4 +- 13 files changed, 604 insertions(+), 498 deletions(-) create mode 100644 crates/matrix-sdk/src/client/builder.rs rename crates/matrix-sdk/src/{client.rs => client/mod.rs} (95%) delete mode 100644 crates/matrix-sdk/src/config/client.rs diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index e434a4cff..b16d6b3ef 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -90,10 +90,9 @@ pub use matrix_sdk; pub use matrix_sdk::ruma; use matrix_sdk::{ bytes::Bytes, - config::ClientConfig, event_handler::{EventHandler, EventHandlerResult, SyncEvent}, reqwest::Url, - Client, Session, + Client, ClientBuildError, ClientBuilder, Session, }; use regex::Regex; use ruma::{ @@ -210,8 +209,8 @@ impl AppService { /// Create new AppService /// /// Also creates and caches a [`Client`] for the [`MainUser`]. - /// The default [`ClientConfig`] is used, if you want to customize it - /// use [`Self::with_config()`] instead. + /// A default [`ClientBuilder`] is used, if you want to customize it + /// use [`with_client_builder()`][Self::with_client_builder] instead. /// /// # Arguments /// @@ -228,19 +227,19 @@ impl AppService { registration: AppServiceRegistration, ) -> Result { let appservice = - Self::with_config(homeserver_url, server_name, registration, ClientConfig::default()) + Self::with_client_builder(homeserver_url, server_name, registration, Client::builder()) .await?; Ok(appservice) } - /// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the - /// [`Client`] - pub async fn with_config( + /// Same as [`new()`][Self::new] but lets you provide a [`ClientBuilder`] + /// for the [`Client`] + pub async fn with_client_builder( homeserver_url: impl TryInto, server_name: impl TryInto, Error = identifiers::Error>, registration: AppServiceRegistration, - client_config: ClientConfig, + builder: ClientBuilder, ) -> Result { let homeserver_url = homeserver_url.try_into()?; let server_name = server_name.try_into()?; @@ -253,7 +252,7 @@ impl AppService { AppService { homeserver_url, server_name, registration, clients, event_handler }; // we create and cache the [`MainUser`] by default - appservice.create_and_cache_client(&sender_localpart, client_config).await?; + appservice.create_and_cache_client(&sender_localpart, builder).await?; Ok(appservice) } @@ -266,8 +265,9 @@ impl AppService { /// /// This method is a singleton that saves the client internally for re-use /// based on the `localpart`. The cached [`Client`] can be retrieved either - /// by calling this method again or by calling [`Self::get_cached_client()`] - /// which is non-async convenience wrapper. + /// by calling this method again or by calling + /// [`get_cached_client()`][Self::get_cached_client] which is non-async + /// convenience wrapper. /// /// Note that if you want to do actions like joining rooms with a virtual /// user it needs to be registered first. `Self::register_virtual_user()` @@ -281,20 +281,20 @@ impl AppService { /// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion pub async fn virtual_user_client(&self, localpart: impl AsRef) -> Result { let client = - self.virtual_user_client_with_config(localpart, ClientConfig::default()).await?; + self.virtual_user_client_with_client_builder(localpart, Client::builder()).await?; Ok(client) } - /// Same as [`Self::virtual_user_client()`] but with the ability to pass in - /// a [`ClientConfig`] + /// Same as [`virtual_user_client()`][Self::virtual_user_client] but with + /// the ability to pass in a [`ClientBuilder`] /// /// Since this method is a singleton follow-up calls with different - /// [`ClientConfig`]s will be ignored. - pub async fn virtual_user_client_with_config( + /// [`ClientBuilder`]s will be ignored. + pub async fn virtual_user_client_with_client_builder( &self, localpart: impl AsRef, - config: ClientConfig, + builder: ClientBuilder, ) -> Result { // TODO: check if localpart is covered by namespace? let localpart = localpart.as_ref(); @@ -302,7 +302,7 @@ impl AppService { let client = if let Some(client) = self.clients.get(localpart) { client.clone() } else { - self.create_and_cache_client(localpart, config).await? + self.create_and_cache_client(localpart, builder).await? }; Ok(client) @@ -311,7 +311,7 @@ impl AppService { async fn create_and_cache_client( &self, localpart: &str, - mut config: ClientConfig, + mut builder: ClientBuilder, ) -> Result { let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?; @@ -319,11 +319,15 @@ impl AppService { // (`sender_localpart`) by default, so we don't need to assert identity // in that case if localpart != self.registration.sender_localpart { - config = config.assert_identity(); + builder = builder.assert_identity(); } - let client = - Client::with_config(self.homeserver_url.clone(), config.appservice_mode()).await?; + let client = builder + .homeserver_url(self.homeserver_url.clone()) + .appservice_mode() + .build() + .await + .map_err(ClientBuildError::assert_valid_builder_args)?; let session = Session { access_token: self.registration.as_token.clone(), @@ -341,8 +345,9 @@ impl AppService { /// Get cached [`Client`] /// /// Will return the client for the given `localpart` if previously - /// constructed with [`Self::virtual_user_client()`] or - /// [`Self::virtual_user_client_with_config()`]. + /// constructed with [`virtual_user_client()`][Self::virtual_user_client] or + /// [`virtual_user_client_with_config()`][Self:: + /// virtual_user_client_with_client_builder]. /// /// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If /// no client for `localpart` is found it will return an Error. diff --git a/crates/matrix-sdk-appservice/tests/tests.rs b/crates/matrix-sdk-appservice/tests/tests.rs index 4092db182..f18c974f2 100644 --- a/crates/matrix-sdk-appservice/tests/tests.rs +++ b/crates/matrix-sdk-appservice/tests/tests.rs @@ -4,8 +4,9 @@ use std::{ }; use matrix_sdk::{ - config::{ClientConfig, RequestConfig}, + config::RequestConfig, ruma::{api::appservice::Registration, events::room::member::SyncRoomMemberEvent}, + Client, }; use matrix_sdk_appservice::*; use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson}; @@ -32,10 +33,17 @@ async fn appservice(registration: Option) -> Result { let homeserver_url = mockito::server_url(); let server_name = "localhost"; - let client_config = - ClientConfig::default().request_config(RequestConfig::default().disable_retry()); + let client_builder = Client::builder() + .request_config(RequestConfig::default().disable_retry()) + .check_supported_versions(false); - AppService::with_config(homeserver_url.as_ref(), server_name, registration, client_config).await + AppService::with_client_builder( + homeserver_url.as_ref(), + server_name, + registration, + client_builder, + ) + .await } #[async_test] diff --git a/crates/matrix-sdk/examples/autojoin.rs b/crates/matrix-sdk/examples/autojoin.rs index 490ce29d1..237532197 100644 --- a/crates/matrix-sdk/examples/autojoin.rs +++ b/crates/matrix-sdk/examples/autojoin.rs @@ -1,13 +1,9 @@ use std::{env, process::exit}; use matrix_sdk::{ - config::{ClientConfig, SyncSettings}, - room::Room, - ruma::events::room::member::StrippedRoomMemberEvent, - Client, + config::SyncSettings, room::Room, ruma::events::room::member::StrippedRoomMemberEvent, Client, }; use tokio::time::{sleep, Duration}; -use url::Url; async fn on_stripped_state_member( room_member: StrippedRoomMemberEvent, @@ -44,11 +40,9 @@ async fn login_and_sync( homeserver_url: String, username: &str, password: &str, -) -> Result<(), matrix_sdk::Error> { - #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))] - let client_config = ClientConfig::new(); - #[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))] - let mut client_config = ClientConfig::new(); +) -> anyhow::Result<()> { + #[allow(unused_mut)] + let mut client_builder = Client::builder().homeserver_url(homeserver_url); #[cfg(feature = "sled_state_store")] { @@ -56,17 +50,16 @@ async fn login_and_sync( let mut home = dirs::home_dir().expect("no home directory found"); home.push("autojoin_bot"); let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; - client_config = client_config.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(Box::new(state_store)); } #[cfg(feature = "indexeddb_state_store")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); - client_config = client_config.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(Box::new(state_store)); } - let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - let client = Client::with_config(homeserver_url, client_config).await.unwrap(); + let client = client_builder.build().await?; client.login(username, password, None, Some("autojoin bot")).await?; @@ -80,7 +73,7 @@ async fn login_and_sync( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = diff --git a/crates/matrix-sdk/examples/command_bot.rs b/crates/matrix-sdk/examples/command_bot.rs index 3028a1f37..747602983 100644 --- a/crates/matrix-sdk/examples/command_bot.rs +++ b/crates/matrix-sdk/examples/command_bot.rs @@ -1,14 +1,13 @@ use std::{env, process::exit}; use matrix_sdk::{ - config::{ClientConfig, SyncSettings}, + config::SyncSettings, room::Room, ruma::events::room::message::{ MessageType, RoomMessageEventContent, SyncRoomMessageEvent, TextMessageEventContent, }, Client, }; -use url::Url; async fn on_room_message(event: SyncRoomMessageEvent, room: Room) { if let Room::Joined(room) = room { @@ -36,11 +35,9 @@ async fn login_and_sync( homeserver_url: String, username: String, password: String, -) -> Result<(), matrix_sdk::Error> { - #[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))] - let client_config = ClientConfig::new(); - #[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))] - let mut client_config = ClientConfig::new(); +) -> anyhow::Result<()> { + #[allow(unused_mut)] + let mut client_builder = Client::builder().homeserver_url(homeserver_url); #[cfg(feature = "sled_state_store")] { @@ -48,19 +45,16 @@ async fn login_and_sync( let mut home = dirs::home_dir().expect("no home directory found"); home.push("party_bot"); let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?; - client_config = client_config.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(Box::new(state_store)); } #[cfg(feature = "indexeddb_state_store")] { let state_store = matrix_sdk_indexeddb::StateStore::open(); - client_config = client_config.state_store(Box::new(state_store)); + client_builder = client_builder.state_store(Box::new(state_store)); } - let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL"); - // create a new Client with the given homeserver url and config - let client = Client::with_config(homeserver_url, client_config).await.unwrap(); - + let client = client_builder.build().await.unwrap(); client.login(&username, &password, None, Some("command bot")).await?; println!("logged in as {}", username); @@ -84,7 +78,7 @@ async fn login_and_sync( } #[tokio::main] -async fn main() -> Result<(), matrix_sdk::Error> { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let (homeserver_url, username, password) = diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs new file mode 100644 index 000000000..1755d84fd --- /dev/null +++ b/crates/matrix-sdk/src/client/builder.rs @@ -0,0 +1,401 @@ +use std::sync::Arc; + +use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore}; +use ruma::{api::client::discover::discover_homeserver, UserId}; +use thiserror::Error; +use url::Url; + +use super::{Client, ClientInner}; +use crate::{ + config::RequestConfig, + http_client::{HttpClient, HttpSend, HttpSettings}, + HttpError, +}; + +/// Builder that allows creating and configuring various parts of a [`Client`]. +/// +/// When setting the `StateStore` it is up to the user to open/connect +/// the storage backend before client creation. +/// +/// # Example +/// +/// ``` +/// use matrix_sdk::Client; +/// // To pass all the request through mitmproxy set the proxy and disable SSL +/// // verification +/// +/// let client_builder = Client::builder() +/// .proxy("http://localhost:8080")? +/// .disable_ssl_verification(); +/// ``` +/// +/// # Example for using a custom http client +/// +/// Note: setting a custom http client will ignore `user_agent`, `proxy`, and +/// `disable_ssl_verification` - you'd need to set these yourself if you want +/// them. +/// +/// ``` +/// use matrix_sdk::Client; +/// use std::sync::Arc; +/// +/// // setting up a custom http client +/// let reqwest_builder = reqwest::ClientBuilder::new() +/// .https_only(true) +/// .no_proxy() +/// .user_agent("MyApp/v3.0"); +/// +/// let client_builder = Client::builder() +/// .http_client(Arc::new(reqwest_builder.build()?)); +/// # anyhow::Ok(()) +/// ``` +#[must_use] +#[derive(Debug)] +pub struct ClientBuilder { + homeserver_cfg: Option, + http_cfg: Option, + store_config: StoreConfig, + request_config: RequestConfig, + respect_login_well_known: bool, + appservice_mode: bool, + check_supported_versions: bool, +} + +impl ClientBuilder { + pub(crate) fn new() -> Self { + Self { + homeserver_cfg: None, + http_cfg: None, + store_config: Default::default(), + request_config: Default::default(), + respect_login_well_known: true, + appservice_mode: false, + check_supported_versions: true, + } + } + + /// Set the homeserver URL to use. + /// + /// This method is mutually exclusive with [`user_id()`][Self::user_id], if + /// you set both whatever was set last will be used. + pub fn homeserver_url(mut self, url: impl AsRef) -> Self { + self.homeserver_cfg = Some(HomeserverConfig::Url(url.as_ref().to_owned())); + self + } + + /// Set the user ID to discover the homeserver from. + /// + /// This method is mutually exclusive with + /// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was + /// set last will be used. + pub fn user_id(mut self, user_id: &UserId) -> Self { + self.homeserver_cfg = Some(HomeserverConfig::UserId(user_id.to_owned())); + self + } + + /// Create a new `ClientConfig` with the given [`StoreConfig`]. + /// + /// The easiest way to get a [`StoreConfig`] is to use the + /// [`make_store_config`] method from the [`store`] module or directly from + /// one of the store crates. + /// + /// # Arguments + /// + /// * `store_config` - The configuration of the store. + /// + /// # Example + /// + /// ``` + /// # use matrix_sdk_base::store::MemoryStore; + /// # let custom_state_store = Box::new(MemoryStore::new()); + /// use matrix_sdk::{Client, config::StoreConfig}; + /// + /// let store_config = StoreConfig::new().state_store(custom_state_store); + /// let client_builder = Client::builder().store_config(store_config); + /// ``` + /// [`make_store_config`]: crate::store::make_store_config + /// [`store`]: crate::store + pub fn store_config(mut self, store_config: StoreConfig) -> Self { + self.store_config = store_config; + self + } + + /// Set a custom implementation of a `StateStore`. + /// + /// The state store should be opened before being set. + pub fn state_store(mut self, store: Box) -> Self { + self.store_config = self.store_config.state_store(store); + self + } + + /// Set a custom implementation of a `CryptoStore`. + /// + /// The crypto store should be opened before being set. + #[cfg(feature = "encryption")] + pub fn crypto_store( + mut self, + store: Box, + ) -> Self { + self.store_config = self.store_config.crypto_store(store); + self + } + + /// Update the client's homeserver URL with the discovery information + /// present in the login response, if any. + pub fn respect_login_well_known(mut self, value: bool) -> Self { + self.respect_login_well_known = value; + self + } + + /// Set the default timeout, fail and retry behavior for all HTTP requests. + pub fn request_config(mut self, request_config: RequestConfig) -> Self { + self.request_config = request_config; + self + } + + /// Set the proxy through which all the HTTP requests should go. + /// + /// Note, only HTTP proxies are supported. + /// + /// # Arguments + /// + /// * `proxy` - The HTTP URL of the proxy. + /// + /// # Example + /// + /// ``` + /// # futures::executor::block_on(async { + /// use matrix_sdk::{Client, config::ClientConfig}; + /// + /// let client_config = ClientConfig::new() + /// .proxy("http://localhost:8080")?; + /// + /// # Result::<_, matrix_sdk::Error>::Ok(()) + /// # }); + /// ``` + #[cfg(not(target_arch = "wasm32"))] + pub fn proxy(mut self, proxy: impl AsRef) -> Self { + self.http_settings().proxy = Some(proxy.as_ref().to_owned()); + self + } + + /// Disable SSL verification for the HTTP requests. + #[cfg(not(target_arch = "wasm32"))] + pub fn disable_ssl_verification(mut self) -> Self { + self.http_settings().disable_ssl_verification = true; + self + } + + /// Set a custom HTTP user agent for the client. + #[cfg(not(target_arch = "wasm32"))] + pub fn user_agent(mut self, user_agent: impl AsRef) -> Self { + self.http_settings().user_agent = Some(user_agent.as_ref().to_owned()); + self + } + + /// Specify an HTTP client to handle sending requests and receiving + /// responses. + /// + /// Any type that implements the `HttpSend` trait can be used to send / + /// receive `http` types. + /// + /// This method is mutually exclusive with + /// [`user_agent()`][Self::user_agent], + pub fn http_client(mut self, client: Arc) -> Self { + self.http_cfg = Some(HttpConfig::Custom(client)); + self + } + + /// Puts the client into application service mode + /// + /// This is low-level functionality. For an high-level API check the + /// `matrix_sdk_appservice` crate. + #[doc(hidden)] + #[cfg(feature = "appservice")] + pub fn appservice_mode(mut self) -> Self { + self.appservice_mode = true; + self + } + + /// All outgoing http requests will have a GET query key-value appended with + /// `user_id` being the key and the `user_id` from the `Session` being + /// the value. Will error if there's no `Session`. This is called + /// [identity assertion] in the Matrix Application Service Spec + /// + /// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion + #[doc(hidden)] + #[cfg(feature = "appservice")] + pub fn assert_identity(mut self) -> Self { + self.request_config.assert_identity = true; + self + } + + /// Specify whether the homeserver functionality should be checked through a + /// get_supported_versions request. + /// + /// This is helpful for test code that doesn't care to mock that endpoint. + #[doc(hidden)] + pub fn check_supported_versions(mut self, value: bool) -> Self { + self.check_supported_versions = value; + self + } + + #[cfg(not(target_arch = "wasm32"))] + fn http_settings(&mut self) -> &mut HttpSettings { + self.http_cfg.get_or_insert_with(Default::default).settings() + } + + /// Create a [`Client`] with the options set on this builder. + /// + /// # Errors + /// + /// This method can fail for two general reasons: + /// + /// * Invalid input: a missing or invalid homeserver URL or invalid proxy + /// URL + /// * HTTP error: If you supplied a user ID instead of a homeserver URL, a + /// server discovery request is made which can fail; if you didn't set + /// [`check_supported_versions(false)`][Self::check_supported_versions], + /// that amounts to another request that can fail + pub async fn build(self) -> Result { + let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?; + + let inner_http_client = match self.http_cfg.unwrap_or_default() { + #[allow(unused_mut)] + HttpConfig::Settings(mut settings) => { + #[cfg(not(target_arch = "wasm32"))] + { + settings.timeout = self.request_config.timeout; + } + + Arc::new(settings.make_client()?) + } + HttpConfig::Custom(c) => c, + }; + + let base_client = BaseClient::with_store_config(self.store_config); + + let mk_http_client = |homeserver| { + HttpClient::new( + inner_http_client.clone(), + homeserver, + base_client.session().clone(), + self.request_config, + ) + }; + + let homeserver = match homeserver_cfg { + HomeserverConfig::Url(url) => url, + HomeserverConfig::UserId(user_id) => { + let homeserver = homeserver_from_user_id(&user_id)?; + let http_client = mk_http_client(Arc::new(RwLock::new(homeserver))); + let well_known = + http_client.send(discover_homeserver::Request::new(), None).await?; + + well_known.homeserver.base_url + } + }; + + let homeserver = Arc::new(RwLock::new(Url::parse(&homeserver)?)); + let http_client = mk_http_client(homeserver.clone()); + + let inner = Arc::new(ClientInner { + homeserver, + http_client, + base_client, + #[cfg(feature = "encryption")] + group_session_locks: Default::default(), + #[cfg(feature = "encryption")] + key_claim_lock: Default::default(), + members_request_locks: Default::default(), + typing_notice_times: Default::default(), + event_handlers: Default::default(), + event_handler_data: Default::default(), + notification_handlers: Default::default(), + appservice_mode: self.appservice_mode, + respect_login_well_known: self.respect_login_well_known, + sync_beat: event_listener::Event::new(), + }); + let client = Client { inner }; + + if self.check_supported_versions { + client.get_supported_versions().await?; + } + + Ok(client) + } +} + +fn homeserver_from_user_id(user_id: &UserId) -> Result { + let homeserver = format!("https://{}", user_id.server_name()); + #[allow(unused_mut)] + let mut result = Url::parse(homeserver.as_str())?; + // Mockito only knows how to test http endpoints: + // https://github.com/lipanski/mockito/issues/127 + #[cfg(test)] + let _ = result.set_scheme("http"); + Ok(result) +} + +#[derive(Debug)] +enum HomeserverConfig { + Url(String), + UserId(Box), +} + +#[derive(Debug)] +enum HttpConfig { + Settings(HttpSettings), + Custom(Arc), +} + +#[cfg(not(target_arch = "wasm32"))] +impl HttpConfig { + fn settings(&mut self) -> &mut HttpSettings { + match self { + Self::Settings(s) => s, + Self::Custom(_) => { + *self = Self::default(); + match self { + Self::Settings(s) => s, + Self::Custom(_) => unreachable!(), + } + } + } + } +} + +impl Default for HttpConfig { + fn default() -> Self { + Self::Settings(HttpSettings::default()) + } +} + +/// Errors that can happen in [`ClientBuilder::build`]. +#[derive(Debug, Error)] +pub enum ClientBuildError { + /// No homeserver or user ID was configured + #[error("no homeserver or user ID was configured")] + MissingHomeserver, + + /// An error encountered when trying to parse the homeserver url. + #[error(transparent)] + Url(#[from] url::ParseError), + + /// Error doing an HTTP request. + #[error(transparent)] + Http(#[from] HttpError), +} + +impl ClientBuildError { + /// Assert that a valid homeserver URL was given to the builder and no other + /// invalid options were specified, which means the only possible error + /// case is [`Self::Http`]. + #[doc(hidden)] + pub fn assert_valid_builder_args(self) -> HttpError { + match self { + ClientBuildError::Http(e) => e, + _ => unreachable!("homeserver URL was asserted to be valid"), + } + } +} diff --git a/crates/matrix-sdk/src/client.rs b/crates/matrix-sdk/src/client/mod.rs similarity index 95% rename from crates/matrix-sdk/src/client.rs rename to crates/matrix-sdk/src/client/mod.rs index 83fd8a0f4..0f06eaabe 100644 --- a/crates/matrix-sdk/src/client.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -44,7 +44,7 @@ use ruma::{ capabilities::{get_capabilities, Capabilities}, device::{delete_devices, get_devices}, directory::{get_public_rooms, get_public_rooms_filtered}, - discover::{discover_homeserver, get_supported_versions}, + discover::get_supported_versions, filter::{create_filter::v3::Request as FilterUploadRequest, FilterDefinition}, media::{create_content, get_content, get_content_thumbnail}, membership::{join_room_by_id, join_room_by_id_or_alias}, @@ -67,13 +67,17 @@ use url::Url; use crate::{ attachment::{AttachmentInfo, Thumbnail}, - config::{ClientConfig, RequestConfig}, + config::RequestConfig, error::{HttpError, HttpResult}, event_handler::{EventHandler, EventHandlerData, EventHandlerResult, EventKind, SyncEvent}, - http_client::{client_with_config, HttpClient}, + http_client::HttpClient, room, Account, Error, Result, }; +mod builder; + +pub use self::builder::{ClientBuildError, ClientBuilder}; + /// A conservative upload speed of 1Mbps const DEFAULT_UPLOAD_SPEED: u64 = 125_000; /// 5 min minimal upload request timeout, used to clamp the request timeout. @@ -139,7 +143,7 @@ pub(crate) struct ClientInner { appservice_mode: bool, /// Whether the client should update its homeserver URL with the discovery /// information present in the login response. - use_discovery_response: bool, + respect_login_well_known: bool, /// An event that can be listened on to wait for a successful sync. The /// event will only be fired if a sync loop is running. Can be used for /// synchronization, e.g. if we send out a request to create a room, we can @@ -161,53 +165,12 @@ impl Client { /// # Arguments /// /// * `homeserver_url` - The homeserver that the client should connect to. - pub async fn new(homeserver_url: Url) -> Result { - let config = ClientConfig::new(); - Client::with_config(homeserver_url, config).await - } - - /// Create a new [`Client`] for the given homeserver and use the given - /// configuration. - /// - /// # Arguments - /// - /// * `homeserver_url` - The homeserver that the client should connect to. - /// - /// * `config` - Configuration for the client. - pub async fn with_config(homeserver_url: Url, config: ClientConfig) -> Result { - let homeserver = Arc::new(RwLock::new(homeserver_url)); - - let client = if let Some(client) = config.client { - client - } else { - Arc::new(client_with_config(&config)?) - }; - - let base_client = BaseClient::with_store_config(config.store_config); - let session = base_client.session().clone(); - - let http_client = - HttpClient::new(client, homeserver.clone(), session, config.request_config); - - let inner = Arc::new(ClientInner { - homeserver, - http_client, - base_client, - #[cfg(feature = "encryption")] - group_session_locks: Default::default(), - #[cfg(feature = "encryption")] - key_claim_lock: Default::default(), - members_request_locks: Default::default(), - typing_notice_times: Default::default(), - event_handlers: Default::default(), - event_handler_data: Default::default(), - notification_handlers: Default::default(), - appservice_mode: config.appservice_mode, - use_discovery_response: config.use_discovery_response, - sync_beat: event_listener::Event::new(), - }); - - Ok(Self { inner }) + pub async fn new(homeserver_url: Url) -> Result { + Self::builder() + .homeserver_url(homeserver_url) + .build() + .await + .map_err(ClientBuildError::assert_valid_builder_args) } /// Create a new [`Client`] using homeserver auto discovery. @@ -240,37 +203,17 @@ impl Client { /// ``` /// /// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri - pub async fn new_from_user_id(user_id: &UserId) -> Result { - let config = ClientConfig::new(); - Client::new_from_user_id_with_config(user_id, config).await + pub async fn new_from_user_id(user_id: &UserId) -> Result { + Self::builder() + .user_id(user_id) + .build() + .await + .map_err(ClientBuildError::assert_valid_builder_args) } - /// Create a new [`Client`] using homeserver auto discovery. - /// - /// This method will create a [`Client`] object that will attempt to - /// discover and configure the homeserver for the given user. Follows the - /// homeserver discovery directions described in the [spec]. - /// - /// # Arguments - /// - /// * `user_id` - The id of the user whose homeserver the client should - /// connect to. - /// - /// * `config` - Configuration for the client. - /// - /// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri - pub async fn new_from_user_id_with_config( - user_id: &UserId, - config: ClientConfig, - ) -> Result { - let homeserver = Client::homeserver_from_user_id(user_id)?; - let client = Client::with_config(homeserver, config).await?; - - let well_known = client.discover_homeserver().await?; - let well_known = Url::parse(well_known.homeserver.base_url.as_ref())?; - client.set_homeserver(well_known).await; - client.get_supported_versions().await?; - Ok(client) + /// Create a new [`ClientBuilder`]. + pub fn builder() -> ClientBuilder { + ClientBuilder::new() } pub(crate) fn base_client(&self) -> &BaseClient { @@ -291,22 +234,6 @@ impl Client { self.base_client().mark_request_as_sent(request_id, response).await } - fn homeserver_from_user_id(user_id: &UserId) -> Result { - let homeserver = format!("https://{}", user_id.server_name()); - #[allow(unused_mut)] - let mut result = Url::parse(homeserver.as_str())?; - // Mockito only knows how to test http endpoints: - // https://github.com/lipanski/mockito/issues/127 - #[cfg(test)] - let _ = result.set_scheme("http"); - Ok(result) - } - - async fn discover_homeserver(&self) -> HttpResult { - self.send(discover_homeserver::Request::new(), Some(RequestConfig::new().disable_retry())) - .await - } - /// Change the homeserver URL used by this client. /// /// # Arguments @@ -472,7 +399,12 @@ impl Client { /// use serde::{Deserialize, Serialize}; /// /// # block_on(async { - /// # let client = Client::new(homeserver).await.unwrap(); + /// # let client = matrix_sdk::Client::builder() + /// # .homeserver_url(homeserver) + /// # .check_supported_versions(false) + /// # .build() + /// # .await + /// # .unwrap(); /// client /// .register_event_handler( /// |ev: SyncRoomMessageEvent, room: Room, client: Client| async move { @@ -586,7 +518,12 @@ impl Client { /// # fn obtain_gui_handle() -> SomeType { SomeType } /// # let homeserver = url::Url::parse("http://localhost:8080").unwrap(); /// # block_on(async { - /// # let client = matrix_sdk::Client::new(homeserver).await.unwrap(); + /// # let client = matrix_sdk::Client::builder() + /// # .homeserver_url(homeserver) + /// # .check_supported_versions(false) + /// # .build() + /// # .await + /// # .unwrap(); /// /// // Handle used to send messages to the UI part of the app /// let my_gui_handle: SomeType = obtain_gui_handle(); @@ -1135,7 +1072,7 @@ impl Client { /// /// * `response` - A successful login response. async fn receive_login_response(&self, response: &login::v3::Response) -> Result<()> { - if self.inner.use_discovery_response { + if self.inner.respect_login_well_known { if let Some(well_known) = &response.well_known { if let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url) { self.set_homeserver(homeserver).await; @@ -2325,6 +2262,7 @@ impl Client { }) } } + // mockito (the http mocking library) is not supported for wasm32 #[cfg(all(test, not(target_arch = "wasm32")))] pub(crate) mod test { @@ -2367,26 +2305,38 @@ pub(crate) mod test { mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId, }; use serde_json::json; + use url::Url; - use super::{Client, Session, Url}; + use super::{Client, ClientBuilder, Session}; use crate::{ attachment::{ AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, Thumbnail, }, - config::{ClientConfig, RequestConfig, SyncSettings}, + config::{RequestConfig, SyncSettings}, HttpError, RoomMember, }; + fn test_client_builder() -> ClientBuilder { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + Client::builder().homeserver_url(homeserver).check_supported_versions(false) + } + + async fn no_retry_test_client() -> Client { + test_client_builder() + .request_config(RequestConfig::new().disable_retry()) + .build() + .await + .unwrap() + } + pub(crate) async fn logged_in_client() -> Client { let session = Session { access_token: "1234".to_owned(), user_id: user_id!("@example:localhost").to_owned(), device_id: device_id!("DEVICEID").to_owned(), }; - let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); - let client = Client::with_config(homeserver, config).await.unwrap(); + let client = no_retry_test_client().await; client.restore_login(session).await.unwrap(); client @@ -2394,12 +2344,8 @@ pub(crate) mod test { #[async_test] async fn set_homeserver() { + let client = no_retry_test_client().await; let homeserver = Url::from_str("http://example.com/").unwrap(); - - let client = Client::new(homeserver).await.unwrap(); - - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - client.set_homeserver(homeserver.clone()).await; assert_eq!(client.homeserver().await, homeserver); @@ -2450,8 +2396,7 @@ pub(crate) mod test { #[async_test] async fn login() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - - let client = Client::new(homeserver.clone()).await.unwrap(); + let client = no_retry_test_client().await; let _m_types = mock("GET", "/_matrix/client/r0/login") .with_status(200) @@ -2482,10 +2427,7 @@ pub(crate) mod test { #[async_test] async fn login_with_discovery() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::new().use_discovery_response(); - - let client = Client::with_config(homeserver, config).await.unwrap(); + let client = no_retry_test_client().await; let _m_login = mock("POST", "/_matrix/client/r0/login") .with_status(200) @@ -2502,10 +2444,7 @@ pub(crate) mod test { #[async_test] async fn login_no_discovery() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::new().use_discovery_response(); - - let client = Client::with_config(homeserver.clone(), config).await.unwrap(); + let client = no_retry_test_client().await; let _m_login = mock("POST", "/_matrix/client/r0/login") .with_status(200) @@ -2517,7 +2456,7 @@ pub(crate) mod test { let logged_in = client.logged_in().await; assert!(logged_in, "Client should be logged in"); - assert_eq!(client.homeserver().await, homeserver); + assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap()); } #[cfg(feature = "sso_login")] @@ -2529,7 +2468,7 @@ pub(crate) mod test { .create(); let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = Client::new(homeserver).await.unwrap(); + let client = no_retry_test_client().await; let idp = crate::client::get_login_types::v3::IdentityProvider::new( "some-id".to_owned(), "idp-name".to_owned(), @@ -2564,9 +2503,7 @@ pub(crate) mod test { #[async_test] async fn login_with_sso_token() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - - let client = Client::new(homeserver).await.unwrap(); + let client = no_retry_test_client().await; let _m = mock("GET", "/_matrix/client/r0/login") .with_status(200) @@ -2610,7 +2547,6 @@ pub(crate) mod test { #[async_test] async fn test_join_leave_room() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned())) @@ -2633,8 +2569,7 @@ pub(crate) mod test { assert!(room.is_some()); // test store reloads with correct room state from the state store - let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry()); - let joined_client = Client::with_config(homeserver, config).await.unwrap(); + let joined_client = no_retry_test_client().await; joined_client.restore_login(session).await.unwrap(); // joined room reloaded from state store @@ -2694,9 +2629,7 @@ pub(crate) mod test { #[async_test] async fn login_error() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::default().request_config(RequestConfig::new().disable_retry()); - let client = Client::with_config(homeserver, config).await.unwrap(); + let client = no_retry_test_client().await; let _m = mock("POST", "/_matrix/client/r0/login") .with_status(403) @@ -2724,8 +2657,7 @@ pub(crate) mod test { #[async_test] async fn register_error() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = Client::new(homeserver).await.unwrap(); + let client = no_retry_test_client().await; let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned())) .with_status(403) @@ -2868,8 +2800,7 @@ pub(crate) mod test { #[async_test] async fn room_search_all() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = Client::new(homeserver).await.unwrap(); + let client = no_retry_test_client().await; let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned())) .with_status(200) @@ -3554,8 +3485,7 @@ pub(crate) mod test { #[async_test] async fn delete_devices() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = Client::new(homeserver).await.unwrap(); + let client = no_retry_test_client().await; let _m = mock("POST", "/_matrix/client/r0/delete_devices") .with_status(401) @@ -3610,10 +3540,13 @@ pub(crate) mod test { #[async_test] async fn retry_limit_http_requests() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3)); - assert!(config.request_config.retry_limit.unwrap() == 3); - let client = Client::with_config(homeserver, config).await.unwrap(); + let client = test_client_builder() + .request_config(RequestConfig::new().retry_limit(3)) + .build() + .await + .unwrap(); + + assert!(client.inner.http_client.request_config.retry_limit.unwrap() == 3); let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create(); @@ -3626,13 +3559,15 @@ pub(crate) mod test { #[async_test] async fn retry_timeout_http_requests() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); // Keep this timeout small so that the test doesn't take long let retry_timeout = Duration::from_secs(5); - let config = ClientConfig::default() - .request_config(RequestConfig::new().retry_timeout(retry_timeout)); - assert!(config.request_config.retry_timeout.unwrap() == retry_timeout); - let client = Client::with_config(homeserver, config).await.unwrap(); + let client = test_client_builder() + .request_config(RequestConfig::new().retry_timeout(retry_timeout)) + .build() + .await + .unwrap(); + + assert!(client.inner.http_client.request_config.retry_timeout.unwrap() == retry_timeout); let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create(); @@ -3646,8 +3581,7 @@ pub(crate) mod test { #[async_test] async fn short_retry_initial_http_requests() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); - let client = Client::new(homeserver).await.unwrap(); + let client = test_client_builder().build().await.unwrap(); let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(3).create(); @@ -3759,7 +3693,6 @@ pub(crate) mod test { #[async_test] async fn test_state_event_getting() { - let homeserver = Url::from_str(&mockito::server_url()).unwrap(); let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); let session = Session { @@ -3828,8 +3761,11 @@ pub(crate) mod test { .with_body(sync.to_string()) .create(); - let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3)); - let client = Client::with_config(homeserver.clone(), config).await.unwrap(); + let client = test_client_builder() + .request_config(RequestConfig::new().retry_limit(3)) + .build() + .await + .unwrap(); client.restore_login(session.clone()).await.unwrap(); let room = client.get_joined_room(room_id); diff --git a/crates/matrix-sdk/src/config/client.rs b/crates/matrix-sdk/src/config/client.rs deleted file mode 100644 index 459fe08be..000000000 --- a/crates/matrix-sdk/src/config/client.rs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright 2021 The Matrix.org Foundation C.I.C. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[allow(unused_imports)] -use std::{ - fmt::{self, Debug}, - path::Path, - sync::Arc, -}; - -use http::header::InvalidHeaderValue; -use matrix_sdk_base::StateStore; - -use crate::{ - config::{RequestConfig, StoreConfig}, - HttpSend, Result, -}; - -/// Configuration for the creation of the `Client`. -/// -/// When setting the `StateStore` it is up to the user to open/connect -/// the storage backend before client creation. -/// -/// # Example -/// -/// ``` -/// use matrix_sdk::config::ClientConfig; -/// // To pass all the request through mitmproxy set the proxy and disable SSL -/// // verification -/// -/// # futures::executor::block_on(async { -/// let client_config = ClientConfig::new() -/// .proxy("http://localhost:8080")? -/// .disable_ssl_verification(); -/// # matrix_sdk::Result::<()>::Ok(()) -/// # }); -/// ``` -/// -/// # Example for using a custom client -/// Note: setting a custom client will ignore `user_agent`, `proxy`, and -/// `disable_ssl_verification` - you'd need to set these yourself if you -/// want them. -/// -/// ``` -/// use matrix_sdk::config::ClientConfig; -/// use reqwest::ClientBuilder; -/// use std::sync::Arc; -/// -/// // setting up a custom builder -/// let builder = ClientBuilder::new() -/// .https_only(true) -/// .no_proxy() -/// .user_agent("MyApp/v3.0"); -/// -/// # futures::executor::block_on(async { -/// let client_config = ClientConfig::new() -/// .client(Arc::new(builder.build()?)); -/// # matrix_sdk::Result::<()>::Ok(()) -/// # }); -/// ``` -#[derive(Default)] -pub struct ClientConfig { - #[cfg(not(target_arch = "wasm32"))] - pub(crate) proxy: Option, - pub(crate) user_agent: Option, - pub(crate) disable_ssl_verification: bool, - pub(crate) store_config: StoreConfig, - pub(crate) request_config: RequestConfig, - pub(crate) client: Option>, - pub(crate) appservice_mode: bool, - pub(crate) use_discovery_response: bool, -} - -#[cfg(not(tarpaulin_include))] -impl Debug for ClientConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut res = fmt.debug_struct("ClientConfig"); - - #[cfg(not(target_arch = "wasm32"))] - let res = res.field("proxy", &self.proxy); - - res.field("user_agent", &self.user_agent) - .field("disable_ssl_verification", &self.disable_ssl_verification) - .field("request_config", &self.request_config) - .finish() - } -} - -impl ClientConfig { - /// Create a new default `ClientConfig`. - pub fn new() -> Self { - Self::default() - } - - /// Create a new `ClientConfig` with the given [`StoreConfig`]. - /// - /// The easiest way to get a [`StoreConfig`] is to use the - /// [`make_store_config`] method from the [`store`] module or directly from - /// one of the store crates. - /// - /// # Arguments - /// - /// * `store_config` - The configuration of the store. - /// - /// # Example - /// - /// ``` - /// # futures::executor::block_on(async { - /// # use matrix_sdk_base::store::MemoryStore; - /// # let custom_state_store = Box::new(MemoryStore::new()); - /// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}}; - /// - /// let store_config = StoreConfig::new().state_store(custom_state_store); - /// let client_config = ClientConfig::new() - /// .store_config(store_config) - /// .use_discovery_response(); - /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) - /// # }); - /// ``` - /// [`make_store_config`]: crate::store::make_store_config - /// [`store`]: crate::store - pub fn store_config(mut self, store_config: StoreConfig) -> Self { - self.store_config = store_config; - self - } - - /// Set the proxy through which all the HTTP requests should go. - /// - /// Note, only HTTP proxies are supported. - /// - /// # Arguments - /// - /// * `proxy` - The HTTP URL of the proxy. - /// - /// # Example - /// - /// ``` - /// # futures::executor::block_on(async { - /// use matrix_sdk::{Client, config::ClientConfig}; - /// - /// let client_config = ClientConfig::new() - /// .proxy("http://localhost:8080")?; - /// - /// # Result::<_, matrix_sdk::Error>::Ok(()) - /// # }); - /// ``` - #[cfg(not(target_arch = "wasm32"))] - pub fn proxy(mut self, proxy: &str) -> Result { - self.proxy = Some(reqwest::Proxy::all(proxy)?); - Ok(self) - } - - /// Disable SSL verification for the HTTP requests. - #[must_use] - pub fn disable_ssl_verification(mut self) -> Self { - self.disable_ssl_verification = true; - self - } - - /// Set a custom HTTP user agent for the client. - pub fn user_agent(mut self, user_agent: &str) -> Result { - self.user_agent = Some(user_agent.to_owned()); - Ok(self) - } - - /// Set a custom implementation of a `StateStore`. - /// - /// The state store should be opened before being set. - pub fn state_store(mut self, store: Box) -> Self { - self.store_config = self.store_config.state_store(store); - self - } - - /// Set the default timeout, fail and retry behavior for all HTTP requests. - #[must_use] - pub fn request_config(mut self, request_config: RequestConfig) -> Self { - self.request_config = request_config; - self - } - - /// Get the [`RequestConfig`] - pub fn get_request_config(&self) -> &RequestConfig { - &self.request_config - } - - /// Specify a client to handle sending requests and receiving responses. - /// - /// Any type that implements the `HttpSend` trait can be used to - /// send/receive `http` types. - #[must_use] - pub fn client(mut self, client: Arc) -> Self { - self.client = Some(client); - self - } - - /// Puts the client into application service mode - /// - /// This is low-level functionality. For an high-level API check the - /// `matrix_sdk_appservice` crate. - #[cfg(feature = "appservice")] - #[must_use] - pub fn appservice_mode(mut self) -> Self { - self.appservice_mode = true; - self - } - - /// Set a custom implementation of a `CryptoStore`. - /// - /// The crypto store should be opened before being set. - #[cfg(feature = "encryption")] - #[must_use] - pub fn crypto_store( - mut self, - store: Box, - ) -> Self { - self.store_config = self.store_config.crypto_store(store); - self - } - - /// Update the client's homeserver URL with the discovery information - /// present in the login response, if any. - #[must_use] - pub fn use_discovery_response(mut self) -> Self { - self.use_discovery_response = true; - self - } - - /// All outgoing http requests will have a GET query key-value appended with - /// `user_id` being the key and the `user_id` from the `Session` being - /// the value. Will error if there's no `Session`. This is called - /// [identity assertion] in the Matrix Application Service Spec - /// - /// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion - #[cfg(feature = "appservice")] - #[must_use] - pub fn assert_identity(mut self) -> Self { - self.request_config.assert_identity = true; - self - } -} diff --git a/crates/matrix-sdk/src/config/mod.rs b/crates/matrix-sdk/src/config/mod.rs index 39ade13e7..4716b25f7 100644 --- a/crates/matrix-sdk/src/config/mod.rs +++ b/crates/matrix-sdk/src/config/mod.rs @@ -14,11 +14,9 @@ //! Configuration to change the behaviour of the [`Client`][crate::Client]. -mod client; mod request; mod sync; -pub use client::ClientConfig; pub use matrix_sdk_base::store::StoreConfig; pub use request::RequestConfig; pub use sync::SyncSettings; diff --git a/crates/matrix-sdk/src/config/request.rs b/crates/matrix-sdk/src/config/request.rs index 2d9c03686..54eedeb40 100644 --- a/crates/matrix-sdk/src/config/request.rs +++ b/crates/matrix-sdk/src/config/request.rs @@ -17,7 +17,7 @@ use std::{ time::Duration, }; -const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); +use crate::http_client::DEFAULT_REQUEST_TIMEOUT; /// Configuration for requests the `Client` makes. /// diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 5f6c2db07..34de706dd 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -168,7 +168,7 @@ to work. 2. To persist the encryption keys, you can use one of the provided backend constructors as described in the documentation of the [`store`] module or you can provide your own backend that implements [`CryptoStore`] in a -[`StoreConfig`] or via [`ClientConfig::crypto_store()`]. +[`StoreConfig`] or via [`ClientBuilder::crypto_store()`]. ## Restoring a client @@ -233,4 +233,4 @@ is **not** supported using the default store. [`store`]: crate::store [`CryptoStore`]: matrix_sdk_base::crypto::store::CryptoStore [`StoreConfig`]: crate::config::StoreConfig -[`ClientConfig::crypto_store()`]: crate::config::ClientConfig::crypto_store() +[`ClientBuilder::crypto_store()`]: crate::ClientBuilder::crypto_store() diff --git a/crates/matrix-sdk/src/http_client.rs b/crates/matrix-sdk/src/http_client.rs index 50e510a1e..89782f93f 100644 --- a/crates/matrix-sdk/src/http_client.rs +++ b/crates/matrix-sdk/src/http_client.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{convert::TryFrom, fmt::Debug, sync::Arc}; +use std::{convert::TryFrom, fmt::Debug, sync::Arc, time::Duration}; use bytes::{Bytes, BytesMut}; use http::Response as HttpResponse; @@ -25,11 +25,9 @@ use ruma::api::{ use tracing::trace; use url::Url; -use crate::{ - config::{ClientConfig, RequestConfig}, - error::HttpError, - Session, -}; +use crate::{config::RequestConfig, error::HttpError, Session}; + +pub(crate) const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); /// Abstraction around the http layer. The allows implementors to use different /// http libraries. @@ -203,33 +201,58 @@ impl HttpClient { } } -/// Build a client with the specified configuration. -pub(crate) fn client_with_config(config: &ClientConfig) -> Result { - let http_client = reqwest::Client::builder(); - +#[derive(Debug)] +pub(crate) struct HttpSettings { #[cfg(not(target_arch = "wasm32"))] - let http_client = { - let http_client = if config.disable_ssl_verification { - http_client.danger_accept_invalid_certs(true) - } else { - http_client + pub(crate) disable_ssl_verification: bool, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) proxy: Option, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) user_agent: Option, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) timeout: Duration, +} + +#[allow(clippy::derivable_impls)] +impl Default for HttpSettings { + fn default() -> Self { + Self { + #[cfg(not(target_arch = "wasm32"))] + disable_ssl_verification: false, + #[cfg(not(target_arch = "wasm32"))] + proxy: None, + #[cfg(not(target_arch = "wasm32"))] + user_agent: None, + #[cfg(not(target_arch = "wasm32"))] + timeout: DEFAULT_REQUEST_TIMEOUT, + } + } +} + +impl HttpSettings { + /// Build a client with the specified configuration. + pub(crate) fn make_client(&self) -> Result { + #[allow(unused_mut)] + let mut http_client = reqwest::Client::builder(); + + #[cfg(not(target_arch = "wasm32"))] + { + if self.disable_ssl_verification { + http_client = http_client.danger_accept_invalid_certs(true) + } + + if let Some(p) = &self.proxy { + http_client = http_client.proxy(reqwest::Proxy::all(p.as_str())?); + } + + let user_agent = + self.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned()); + + http_client = http_client.user_agent(user_agent).timeout(self.timeout); }; - let http_client = match &config.proxy { - Some(p) => http_client.proxy(p.clone()), - None => http_client, - }; - - let user_agent = config.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned()); - - http_client.user_agent(user_agent).timeout(config.request_config.timeout) - }; - - #[cfg(target_arch = "wasm32")] - #[allow(unused)] - let _ = config; - - Ok(http_client.build()?) + Ok(http_client.build()?) + } } async fn response_to_http_response( diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index bb0c55640..ddc9bc127 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -57,7 +57,7 @@ mod sync; pub mod encryption; pub use account::Account; -pub use client::{Client, LoopCtrl}; +pub use client::{Client, ClientBuildError, ClientBuilder, LoopCtrl}; #[cfg(feature = "image_proc")] pub use error::ImageError; pub use error::{Error, HttpError, HttpResult, Result}; diff --git a/crates/matrix-sdk/src/store.rs b/crates/matrix-sdk/src/store.rs index 60d123463..422b7d3d7 100644 --- a/crates/matrix-sdk/src/store.rs +++ b/crates/matrix-sdk/src/store.rs @@ -24,10 +24,10 @@ //! implementation for WebAssembly. //! //! Both options provide a `make_store_config` convenience method to create a -//! [`StoreConfig`] for [`ClientConfig::store_config()`]. +//! [`StoreConfig`] for [`ClientBuilder::store_config()`]. //! //! [`StoreConfig`]: crate::config::StoreConfig -//! [`ClientConfig::store_config()`]: crate::config::ClientConfig::store_config() +//! [`ClientBuilder::store_config()`]: crate::ClientBuilder::store_config #[cfg(any(feature = "indexeddb_state_store", feature = "indexeddb_cryptostore"))] pub use matrix_sdk_indexeddb::*; From 81d07d886c4b8226ed94b35defb3c668398fb703 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 11 Mar 2022 18:30:29 +0100 Subject: [PATCH 34/36] Rename Client::{new_from_user_id => for_user_id} --- crates/matrix-sdk/README.md | 2 +- crates/matrix-sdk/src/client/builder.rs | 8 ++++---- crates/matrix-sdk/src/client/mod.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk/README.md b/crates/matrix-sdk/README.md index 559794933..9dd733517 100644 --- a/crates/matrix-sdk/README.md +++ b/crates/matrix-sdk/README.md @@ -34,7 +34,7 @@ use matrix_sdk::{ #[tokio::main] async fn main() -> Result<()> { let alice = user_id!("@alice:example.org"); - let client = Client::new_from_user_id(alice).await?; + let client = Client::for_user_id(alice).await?; // First we need to log in. client.login(alice, "password", None, None).await?; diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 1755d84fd..6152c5cfe 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -25,7 +25,7 @@ use crate::{ /// // verification /// /// let client_builder = Client::builder() -/// .proxy("http://localhost:8080")? +/// .proxy("http://localhost:8080") /// .disable_ssl_verification(); /// ``` /// @@ -165,10 +165,10 @@ impl ClientBuilder { /// /// ``` /// # futures::executor::block_on(async { - /// use matrix_sdk::{Client, config::ClientConfig}; + /// use matrix_sdk::Client; /// - /// let client_config = ClientConfig::new() - /// .proxy("http://localhost:8080")?; + /// let client_config = Client::builder() + /// .proxy("http://localhost:8080"); /// /// # Result::<_, matrix_sdk::Error>::Ok(()) /// # }); diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 0f06eaabe..97db68adf 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -195,7 +195,7 @@ impl Client { /// let alice = UserId::parse("@alice:example.org")?; /// /// // Now let's try to discover the homeserver and create a client object. - /// let client = Client::new_from_user_id(&alice).await?; + /// let client = Client::for_user_id(&alice).await?; /// /// // Finally let's try to login. /// client.login(alice, "password", None, None).await?; @@ -203,7 +203,7 @@ impl Client { /// ``` /// /// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri - pub async fn new_from_user_id(user_id: &UserId) -> Result { + pub async fn for_user_id(user_id: &UserId) -> Result { Self::builder() .user_id(user_id) .build() @@ -2368,7 +2368,7 @@ pub(crate) mod test { .with_status(200) .with_body(test_json::VERSIONS.to_string()) .create(); - let client = Client::new_from_user_id(&alice).await.unwrap(); + let client = Client::for_user_id(&alice).await.unwrap(); assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); } @@ -2387,7 +2387,7 @@ pub(crate) mod test { .create(); assert!( - Client::new_from_user_id(&alice).await.is_err(), + Client::for_user_id(&alice).await.is_err(), "Creating a client from a user ID should fail when the \ .well-known server returns no version information." ); From d94ba8c64fbe9d3419d8c0fe289be65711c3265c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 14 Mar 2022 09:47:31 +0100 Subject: [PATCH 35/36] Remove Client::for_user_id --- crates/matrix-sdk/README.md | 6 ++--- crates/matrix-sdk/src/client/mod.rs | 42 ++--------------------------- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/crates/matrix-sdk/README.md b/crates/matrix-sdk/README.md index 9dd733517..298658ac2 100644 --- a/crates/matrix-sdk/README.md +++ b/crates/matrix-sdk/README.md @@ -27,14 +27,14 @@ This is demonstrated in the example below. ```rust,no_run use std::convert::TryFrom; use matrix_sdk::{ - Client, config::SyncSettings, Result, + Client, config::SyncSettings, ruma::{user_id, events::room::message::SyncRoomMessageEvent}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> anyhow::Result<()> { let alice = user_id!("@alice:example.org"); - let client = Client::for_user_id(alice).await?; + let client = Client::builder().user_id(alice).build().await?; // First we need to log in. client.login(alice, "password", None, None).await?; diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 97db68adf..aac63b522 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -173,44 +173,6 @@ impl Client { .map_err(ClientBuildError::assert_valid_builder_args) } - /// Create a new [`Client`] using homeserver auto discovery. - /// - /// This method will create a [`Client`] object that will attempt to - /// discover and configure the homeserver for the given user. Follows the - /// homeserver discovery directions described in the [spec]. - /// - /// # Arguments - /// - /// * `user_id` - The id of the user whose homeserver the client should - /// connect to. - /// - /// # Example - /// ```no_run - /// # use std::convert::TryFrom; - /// # use futures::executor::block_on; - /// # block_on(async { - /// use matrix_sdk::{Client, ruma::UserId}; - /// - /// // First let's try to construct an user id, presumably from user input. - /// let alice = UserId::parse("@alice:example.org")?; - /// - /// // Now let's try to discover the homeserver and create a client object. - /// let client = Client::for_user_id(&alice).await?; - /// - /// // Finally let's try to login. - /// client.login(alice, "password", None, None).await?; - /// # Result::<_, matrix_sdk::Error>::Ok(()) }); - /// ``` - /// - /// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri - pub async fn for_user_id(user_id: &UserId) -> Result { - Self::builder() - .user_id(user_id) - .build() - .await - .map_err(ClientBuildError::assert_valid_builder_args) - } - /// Create a new [`ClientBuilder`]. pub fn builder() -> ClientBuilder { ClientBuilder::new() @@ -2368,7 +2330,7 @@ pub(crate) mod test { .with_status(200) .with_body(test_json::VERSIONS.to_string()) .create(); - let client = Client::for_user_id(&alice).await.unwrap(); + let client = Client::builder().user_id(&alice).build().await.unwrap(); assert_eq!(client.homeserver().await, Url::parse(server_url.as_ref()).unwrap()); } @@ -2387,7 +2349,7 @@ pub(crate) mod test { .create(); assert!( - Client::for_user_id(&alice).await.is_err(), + Client::builder().user_id(&alice).build().await.is_err(), "Creating a client from a user ID should fail when the \ .well-known server returns no version information." ); From a2ad7741127afaa9038ac4d9f008316fd3d415c9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 14 Mar 2022 09:54:39 +0100 Subject: [PATCH 36/36] Add ClientBuilder::server_name --- crates/matrix-sdk/src/client/builder.rs | 37 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index 6152c5cfe..5dcdacaf7 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore}; -use ruma::{api::client::discover::discover_homeserver, UserId}; +use ruma::{api::client::discover::discover_homeserver, ServerName, UserId}; use thiserror::Error; use url::Url; @@ -85,11 +85,23 @@ impl ClientBuilder { /// Set the user ID to discover the homeserver from. /// + /// `builder.user_id(id)` is a shortcut for + /// `builder.server_name(id.server_name())`. + /// /// This method is mutually exclusive with /// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was /// set last will be used. - pub fn user_id(mut self, user_id: &UserId) -> Self { - self.homeserver_cfg = Some(HomeserverConfig::UserId(user_id.to_owned())); + pub fn user_id(self, user_id: &UserId) -> Self { + self.server_name(user_id.server_name()) + } + + /// Set the server name to discover the homeserver from. + /// + /// This method is mutually exclusive with + /// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was + /// set last will be used. + pub fn server_name(mut self, server_name: &ServerName) -> Self { + self.homeserver_cfg = Some(HomeserverConfig::ServerName(server_name.to_owned())); self } @@ -286,8 +298,8 @@ impl ClientBuilder { let homeserver = match homeserver_cfg { HomeserverConfig::Url(url) => url, - HomeserverConfig::UserId(user_id) => { - let homeserver = homeserver_from_user_id(&user_id)?; + HomeserverConfig::ServerName(server_name) => { + let homeserver = homeserver_from_name(&server_name)?; let http_client = mk_http_client(Arc::new(RwLock::new(homeserver))); let well_known = http_client.send(discover_homeserver::Request::new(), None).await?; @@ -326,21 +338,22 @@ impl ClientBuilder { } } -fn homeserver_from_user_id(user_id: &UserId) -> Result { - let homeserver = format!("https://{}", user_id.server_name()); - #[allow(unused_mut)] - let mut result = Url::parse(homeserver.as_str())?; +fn homeserver_from_name(server_name: &ServerName) -> Result { + #[cfg(not(test))] + let homeserver = format!("https://{}", server_name); + // Mockito only knows how to test http endpoints: // https://github.com/lipanski/mockito/issues/127 #[cfg(test)] - let _ = result.set_scheme("http"); - Ok(result) + let homeserver = format!("http://{}", server_name); + + Url::parse(&homeserver) } #[derive(Debug)] enum HomeserverConfig { Url(String), - UserId(Box), + ServerName(Box), } #[derive(Debug)]