Skip to main content

matrix_sdk_sqlite/
crypto_store.rs

1// Copyright 2022, 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    collections::HashMap,
17    fmt,
18    ops::Deref,
19    path::{Path, PathBuf},
20    sync::{Arc, RwLock},
21};
22
23use async_trait::async_trait;
24use deadpool::managed::PoolConfig;
25use matrix_sdk_base::cross_process_lock::CrossProcessLockGeneration;
26use matrix_sdk_crypto::{
27    Account, DeviceData, GossipRequest, GossippedSecret, SecretInfo, TrackedUser, UserIdentityData,
28    olm::{
29        InboundGroupSession, OutboundGroupSession, PickledInboundGroupSession,
30        PrivateCrossSigningIdentity, SenderDataType, Session, StaticAccountData,
31    },
32    store::{
33        CryptoStore,
34        types::{
35            BackupKeys, Changes, DehydratedDeviceKey, PendingChanges, RoomKeyCounts,
36            RoomKeyWithheldEntry, RoomPendingKeyBundleDetails, RoomSettings,
37            StoredRoomKeyBundleData,
38        },
39    },
40};
41use matrix_sdk_store_encryption::StoreCipher;
42use ruma::{
43    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, RoomId, TransactionId, UserId,
44    events::secret::request::SecretName,
45};
46use rusqlite::{OptionalExtension, named_params, params_from_iter};
47use tokio::{
48    fs,
49    sync::{Mutex, OwnedMutexGuard},
50};
51use tracing::{debug, instrument, warn};
52use vodozemac::Curve25519PublicKey;
53use zeroize::Zeroizing;
54
55use crate::{
56    OpenStoreError, RuntimeConfig, Secret, SqliteStoreConfig,
57    connection::{
58        self, Connection as SqliteAsyncConn, Pool as SqlitePool, SqliteConnections,
59        SqliteConnectionsInner,
60    },
61    error::{Error, Result},
62    utils::{
63        EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
64        SqliteKeyValueStoreConnExt, map_interact_err,
65    },
66};
67
68/// The database name.
69const DATABASE_NAME: &str = "matrix-sdk-crypto.sqlite3";
70
71/// An SQLite-based crypto store.
72#[derive(Clone)]
73pub struct SqliteCryptoStore {
74    store_cipher: Option<Arc<StoreCipher>>,
75
76    /// `Some` when active, `None` when closed. The outer `Mutex` serialises
77    /// close/reopen with connection access.
78    connections: Arc<SqliteConnections>,
79
80    /// Retained so we can rebuild the pool on reopen.
81    db_path: PathBuf,
82
83    /// Retained so we can rebuild the pool on reopen.
84    pool_config: PoolConfig,
85
86    /// Retained so we can re-apply runtime config on reopen.
87    runtime_config: RuntimeConfig,
88
89    // DB values cached in memory
90    static_account: Arc<RwLock<Option<StaticAccountData>>>,
91    save_changes_lock: Arc<Mutex<()>>,
92}
93
94#[cfg(not(tarpaulin_include))]
95impl fmt::Debug for SqliteCryptoStore {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.debug_struct("SqliteCryptoStore").finish_non_exhaustive()
98    }
99}
100
101impl EncryptableStore for SqliteCryptoStore {
102    fn get_cypher(&self) -> Option<&StoreCipher> {
103        self.store_cipher.as_deref()
104    }
105}
106
107impl SqliteCryptoStore {
108    /// Create an `SqliteCryptoStore` struct without trying to create the
109    /// database or migrate to a newer version. This is only for use internally,
110    /// and for testing.
111    ///
112    /// # Arguments
113    ///
114    /// - `secret` - The secret used to encrypt the data.
115    /// - `pool` - A connection pool to use for reading from the store.
116    /// - `conn` - The connection to use for writing to the store.
117    pub(crate) async fn create_raw(
118        secret: Option<Secret>,
119        pool: SqlitePool,
120        conn: SqliteAsyncConn,
121        pool_config: PoolConfig,
122        runtime_config: RuntimeConfig,
123    ) -> Result<Self, OpenStoreError> {
124        let store_cipher = match secret {
125            Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
126            None => None,
127        };
128
129        let db_path = pool.manager().database_path.clone();
130
131        Ok(Self {
132            store_cipher,
133            connections: Arc::new(
134                SqliteConnections::new(SqliteConnectionsInner {
135                    pool,
136                    write_connection: Arc::new(Mutex::new(conn)),
137                })
138                .await,
139            ),
140            db_path,
141            pool_config,
142            runtime_config,
143            static_account: Arc::new(RwLock::new(None)),
144            save_changes_lock: Default::default(),
145        })
146    }
147
148    /// Open the SQLite-based crypto store at the given path using the given
149    /// passphrase to encrypt private data.
150    pub async fn open(
151        path: impl AsRef<Path>,
152        passphrase: Option<&str>,
153    ) -> Result<Self, OpenStoreError> {
154        Self::open_with_config(&SqliteStoreConfig::new(path).passphrase(passphrase)).await
155    }
156
157    /// Open the SQLite-based crypto store at the given path using the given key
158    /// to encrypt private data.
159    pub async fn open_with_key(
160        path: impl AsRef<Path>,
161        key: Option<&[u8]>,
162    ) -> Result<Self, OpenStoreError> {
163        Self::open_with_config(&SqliteStoreConfig::new(path).key(key)).await
164    }
165
166    /// Open the SQLite-based crypto store with the config open config.
167    pub async fn open_with_config(config: &SqliteStoreConfig) -> Result<Self, OpenStoreError> {
168        fs::create_dir_all(&config.path).await.map_err(OpenStoreError::CreateDir)?;
169
170        let pool = config.build_pool_of_connections(DATABASE_NAME)?;
171        let pool_config = config.pool_config();
172        let runtime_config = config.runtime_config();
173
174        let this =
175            Self::open_with_pool(pool, config.secret.clone(), pool_config, runtime_config).await?;
176        this.read().await?.apply_runtime_config(runtime_config).await?;
177
178        Ok(this)
179    }
180
181    /// Create an SQLite-based crypto store using the given SQLite database
182    /// pool. The given secret will be used to encrypt private data.
183    async fn open_with_pool(
184        pool: SqlitePool,
185        secret: Option<Secret>,
186        pool_config: PoolConfig,
187        runtime_config: RuntimeConfig,
188    ) -> Result<Self, OpenStoreError> {
189        let conn = pool.get().await?;
190
191        let version = conn.db_version().await?;
192        debug!("Opened sqlite store with version {}", version);
193
194        let version = initialize_store(&conn, version).await?;
195
196        let store = Self::create_raw(secret, pool, conn, pool_config, runtime_config).await?;
197
198        run_migrations(&store, version, None).await?;
199
200        store.write().await?.wal_checkpoint().await;
201
202        Ok(store)
203    }
204
205    fn deserialize_and_unpickle_inbound_group_session(
206        &self,
207        value: Vec<u8>,
208        backed_up: bool,
209    ) -> Result<InboundGroupSession> {
210        let mut pickle: PickledInboundGroupSession = self.deserialize_value(&value)?;
211
212        // The `backed_up` SQL column is the source of truth, because we update
213        // it inside `mark_inbound_group_sessions_as_backed_up` and don't update
214        // the pickled value inside the `data` column (until now, when we are
215        // puling it out of the DB).
216        pickle.backed_up = backed_up;
217
218        Ok(InboundGroupSession::from_pickle(pickle)?)
219    }
220
221    fn deserialize_key_request(&self, value: &[u8], sent_out: bool) -> Result<GossipRequest> {
222        let mut request: GossipRequest = self.deserialize_value(value)?;
223        // sent_out SQL column is source of truth, sent_out field in serialized
224        // value needed for other stores though
225        request.sent_out = sent_out;
226        Ok(request)
227    }
228
229    fn get_static_account(&self) -> Option<StaticAccountData> {
230        self.static_account.read().unwrap().clone()
231    }
232
233    /// Acquire a connection for executing read operations.
234    #[instrument(skip_all)]
235    async fn read(&self) -> Result<SqliteAsyncConn> {
236        let pool = {
237            let guard = self.connections.lock().await;
238            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
239            conns.pool.clone()
240        };
241        Ok(pool.get().await?)
242    }
243
244    /// Acquire a connection for executing write operations.
245    #[instrument(skip_all)]
246    pub(crate) async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
247        let write_connection = {
248            let guard = self.connections.lock().await;
249            let conns = guard.as_ref().ok_or(Error::StoreClosed)?;
250            conns.write_connection.clone()
251        };
252        Ok(write_connection.lock_owned().await)
253    }
254}
255
256const DATABASE_VERSION: u8 = 19;
257
258/// key for the dehydrated device pickle key in the key/value table.
259const DEHYDRATED_DEVICE_PICKLE_KEY: &str = "dehydrated_device_pickle_key";
260
261/// Initialize the database to version 1
262///
263/// This must be done before creating the store cipher, because the store cipher
264/// requires the `kv` table.
265///
266/// # Arguments
267///
268/// - `conn` - The connection to use.
269/// - `version` - the current version of the database.
270pub(crate) async fn initialize_store(conn: &SqliteAsyncConn, version: u8) -> Result<u8> {
271    if version == 0 {
272        debug!("Creating database");
273    } else if version < DATABASE_VERSION {
274        debug!(version, new_version = DATABASE_VERSION, "Upgrading database");
275    } else {
276        return Ok(version);
277    }
278
279    if version < 1 {
280        debug!("Creating database");
281        // First turn on WAL mode, this can't be done in the transaction, it
282        // fails with the error message: "cannot change into wal mode from
283        // within a transaction".
284        conn.execute_batch("PRAGMA journal_mode = wal;").await?;
285        conn.with_transaction(|txn| {
286            txn.execute_batch(include_str!("../migrations/crypto_store/001_init.sql"))?;
287            txn.set_db_version(1)
288        })
289        .await?;
290        return Ok(1);
291    }
292
293    Ok(version)
294}
295
296/// Run migrations for the given version of the database.
297///
298/// # Arguments
299///
300/// - `store` - The store to run the migrations on
301/// - `version` - The current version of the database.
302/// - `max_version` - The maximum version that the database will be migrated to.
303///   Only used for testing, so will only be checked for the versions that are
304///   needed for tests.
305pub(crate) async fn run_migrations(
306    store: &SqliteCryptoStore,
307    version: u8,
308    max_version: Option<u8>,
309) -> Result<()> {
310    let conn = store.write().await?;
311
312    if version < 2 {
313        debug!("Upgrading database to version 2");
314        conn.with_transaction(|txn| {
315            txn.execute_batch(include_str!("../migrations/crypto_store/002_reset_olm_hash.sql"))?;
316            txn.set_db_version(2)
317        })
318        .await?;
319    }
320
321    if version < 3 {
322        debug!("Upgrading database to version 3");
323        conn.with_transaction(|txn| {
324            txn.execute_batch(include_str!("../migrations/crypto_store/003_room_settings.sql"))?;
325            txn.set_db_version(3)
326        })
327        .await?;
328    }
329
330    if version < 4 {
331        debug!("Upgrading database to version 4");
332        conn.with_transaction(|txn| {
333            txn.execute_batch(include_str!(
334                "../migrations/crypto_store/004_drop_outbound_group_sessions.sql"
335            ))?;
336            txn.set_db_version(4)
337        })
338        .await?;
339    }
340
341    if version < 5 {
342        debug!("Upgrading database to version 5");
343        conn.with_transaction(|txn| {
344            txn.execute_batch(include_str!("../migrations/crypto_store/005_withheld_code.sql"))?;
345            txn.set_db_version(5)
346        })
347        .await?;
348    }
349
350    if version < 6 {
351        debug!("Upgrading database to version 6");
352        conn.with_transaction(|txn| {
353            txn.execute_batch(include_str!(
354                "../migrations/crypto_store/006_drop_outbound_group_sessions.sql"
355            ))?;
356            txn.set_db_version(6)
357        })
358        .await?;
359    }
360
361    if version < 7 {
362        debug!("Upgrading database to version 7");
363        conn.with_transaction(|txn| {
364            txn.execute_batch(include_str!("../migrations/crypto_store/007_lock_leases.sql"))?;
365            txn.set_db_version(7)
366        })
367        .await?;
368    }
369
370    if version < 8 {
371        debug!("Upgrading database to version 8");
372        conn.with_transaction(|txn| {
373            txn.execute_batch(include_str!("../migrations/crypto_store/008_secret_inbox.sql"))?;
374            txn.set_db_version(8)
375        })
376        .await?;
377    }
378
379    if version < 9 {
380        debug!("Upgrading database to version 9");
381        conn.with_transaction(|txn| {
382            txn.execute_batch(include_str!(
383                "../migrations/crypto_store/009_inbound_group_session_sender_key_sender_data_type.sql"
384            ))?;
385            txn.set_db_version(9)
386        })
387        .await?;
388    }
389
390    if version < 10 {
391        debug!("Upgrading database to version 10");
392        conn.with_transaction(|txn| {
393            txn.execute_batch(include_str!(
394                "../migrations/crypto_store/010_received_room_key_bundles.sql"
395            ))?;
396            txn.set_db_version(10)
397        })
398        .await?;
399    }
400
401    if version < 11 {
402        debug!("Upgrading database to version 11");
403        conn.with_transaction(|txn| {
404            txn.execute_batch(include_str!(
405                "../migrations/crypto_store/011_received_room_key_bundles_with_curve_key.sql"
406            ))?;
407            txn.set_db_version(11)
408        })
409        .await?;
410    }
411
412    if version < 12 {
413        debug!("Upgrading database to version 12");
414        conn.with_transaction(|txn| {
415            txn.execute_batch(include_str!(
416                "../migrations/crypto_store/012_withheld_code_by_room.sql"
417            ))?;
418            txn.set_db_version(12)
419        })
420        .await?;
421    }
422
423    if version < 13 {
424        debug!("Upgrading database to version 13");
425        conn.with_transaction(|txn| {
426            txn.execute_batch(include_str!(
427                "../migrations/crypto_store/013_lease_locks_with_generation.sql"
428            ))?;
429            txn.set_db_version(13)
430        })
431        .await?;
432    }
433
434    if version < 14 {
435        debug!("Upgrading database to version 14");
436        conn.with_transaction(|txn| {
437            txn.execute_batch(include_str!(
438                "../migrations/crypto_store/014_room_key_backups_fully_downloaded.sql"
439            ))?;
440            txn.set_db_version(14)
441        })
442        .await?;
443    }
444
445    if version < 15 {
446        debug!("Upgrading database to version 15");
447        conn.with_transaction(|txn| {
448            txn.execute_batch(include_str!(
449                "../migrations/crypto_store/015_rooms_pending_key_bundle.sql"
450            ))?;
451            txn.set_db_version(15)
452        })
453        .await?;
454    }
455
456    if version < 16 {
457        debug!("Upgrading database to version 16");
458        conn.with_transaction(|txn| {
459            txn.execute_batch(include_str!(
460                "../migrations/crypto_store/016_remove_old_generation_counter.sql"
461            ))?;
462            txn.set_db_version(16)
463        })
464        .await?;
465    }
466
467    if max_version.is_some_and(|max_version| max_version < 17) {
468        return Ok(());
469    }
470
471    if version < 17 {
472        debug!("Upgrading database to version 17");
473        let store = store.clone();
474        conn.with_transaction(move |txn| {
475            txn.execute_batch(include_str!(
476                "../migrations/crypto_store/017_add_new_secrets_inbox.sql"
477            ))?;
478            let mut select_query = txn.prepare("SELECT data FROM secrets")?;
479            let mut secrets = select_query.query([])?;
480            let mut insert_query = txn.prepare(
481                "INSERT OR IGNORE INTO secrets_inbox (secret_name, secret)
482            VALUES (?1, ?2)",
483            )?;
484            while let Some(row) = secrets.next()? {
485                let Ok(secret) =
486                    store.deserialize_json::<GossippedSecret>(row.get::<_, Vec<u8>>(0)?.as_ref())
487                else {
488                    continue;
489                };
490                let Ok(encoded_secret) = store.serialize_json(&secret.event.content.secret) else {
491                    continue;
492                };
493                insert_query.execute((
494                    store.encode_key("secrets_inbox", secret.secret_name.to_string()),
495                    &encoded_secret,
496                ))?;
497            }
498            txn.execute_batch(include_str!(
499                "../migrations/crypto_store/017_drop_old_secrets_inbox.sql"
500            ))?;
501            txn.set_db_version(17)
502        })
503        .await?;
504    }
505
506    if version < 18 {
507        debug!("Upgrading database to version 18");
508        let store = store.clone();
509        conn.with_transaction(move |txn| {
510            txn.execute_batch(include_str!(
511                "../migrations/crypto_store/018_add_gossip_request_info.sql"
512            ))?;
513            let mut select_query =
514                txn.prepare("SELECT request_id, sent_out, data FROM key_requests")?;
515            let mut requests = select_query.query([])?;
516            let mut update_query =
517                txn.prepare("UPDATE OR REPLACE key_requests SET info = ?1 WHERE request_id = ?2")?;
518            while let Some(row) = requests.next()? {
519                let Ok(request) = store.deserialize_key_request(
520                    row.get::<_, Vec<u8>>(2)?.as_ref(),
521                    row.get::<_, bool>(1)?,
522                ) else {
523                    continue;
524                };
525                let info = store.encode_key("key_requests", request.info.as_key());
526                update_query.execute((info, row.get::<_, Vec<u8>>(0)?))?;
527            }
528            txn.set_db_version(18)
529        })
530        .await?;
531    }
532
533    if version < 19 {
534        debug!("Upgrading database to version 19");
535        // Remove the sliding sync `pos` value stored in the crypto store. There
536        // was recently an event cache migration that emptied the cache but
537        // never reset the `pos` value, this fixes it.
538        let user_id = store.load_account().await?.map(|account| account.user_id.clone());
539
540        conn.with_transaction(move |txn| {
541            if let Some(user_id) = user_id {
542                txn.clear_kv(&format!("sliding_sync_store::room-list::{user_id}::instance"))?;
543            }
544            txn.set_db_version(19)
545        })
546        .await?;
547    }
548
549    Ok(())
550}
551
552trait SqliteConnectionExt {
553    fn set_session(
554        &self,
555        session_id: &[u8],
556        sender_key: &[u8],
557        data: &[u8],
558    ) -> rusqlite::Result<()>;
559
560    fn set_inbound_group_session(
561        &self,
562        room_id: &[u8],
563        session_id: &[u8],
564        data: &[u8],
565        backed_up: bool,
566        sender_key: Option<&[u8]>,
567        sender_data_type: Option<u8>,
568    ) -> rusqlite::Result<()>;
569
570    fn set_outbound_group_session(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
571
572    fn set_device(&self, user_id: &[u8], device_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
573    fn delete_device(&self, user_id: &[u8], device_id: &[u8]) -> rusqlite::Result<()>;
574
575    fn set_identity(&self, user_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
576
577    fn add_olm_hash(&self, data: &[u8]) -> rusqlite::Result<()>;
578
579    fn set_key_request(
580        &self,
581        request_id: &[u8],
582        sent_out: bool,
583        data: &[u8],
584        info: &[u8],
585    ) -> rusqlite::Result<()>;
586
587    fn set_direct_withheld(
588        &self,
589        session_id: &[u8],
590        room_id: &[u8],
591        data: &[u8],
592    ) -> rusqlite::Result<()>;
593
594    fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
595
596    fn set_secret(&self, request_id: &[u8], data: &[u8]) -> rusqlite::Result<()>;
597
598    fn set_received_room_key_bundle(
599        &self,
600        room_id: &[u8],
601        user_id: &[u8],
602        data: &[u8],
603    ) -> rusqlite::Result<()>;
604
605    fn set_has_downloaded_all_room_keys(&self, room_id: &[u8]) -> rusqlite::Result<()>;
606
607    fn set_room_pending_key_bundle(
608        &self,
609        room_id: &[u8],
610        details: Option<&[u8]>,
611    ) -> rusqlite::Result<()>;
612}
613
614impl SqliteConnectionExt for rusqlite::Connection {
615    fn set_session(
616        &self,
617        session_id: &[u8],
618        sender_key: &[u8],
619        data: &[u8],
620    ) -> rusqlite::Result<()> {
621        self.execute(
622            "INSERT INTO session (session_id, sender_key, data)
623             VALUES (?1, ?2, ?3)
624             ON CONFLICT (session_id) DO UPDATE SET data = ?3",
625            (session_id, sender_key, data),
626        )?;
627        Ok(())
628    }
629
630    fn set_inbound_group_session(
631        &self,
632        room_id: &[u8],
633        session_id: &[u8],
634        data: &[u8],
635        backed_up: bool,
636        sender_key: Option<&[u8]>,
637        sender_data_type: Option<u8>,
638    ) -> rusqlite::Result<()> {
639        self.execute(
640            "INSERT INTO inbound_group_session (session_id, room_id, data, backed_up, sender_key, sender_data_type) \
641             VALUES (?1, ?2, ?3, ?4, ?5, ?6)
642             ON CONFLICT (session_id) DO UPDATE SET data = ?3, backed_up = ?4, sender_key = ?5, sender_data_type = ?6",
643            (session_id, room_id, data, backed_up, sender_key, sender_data_type),
644        )?;
645        Ok(())
646    }
647
648    fn set_outbound_group_session(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()> {
649        self.execute(
650            "INSERT INTO outbound_group_session (room_id, data) \
651             VALUES (?1, ?2)
652             ON CONFLICT (room_id) DO UPDATE SET data = ?2",
653            (room_id, data),
654        )?;
655        Ok(())
656    }
657
658    fn set_device(&self, user_id: &[u8], device_id: &[u8], data: &[u8]) -> rusqlite::Result<()> {
659        self.execute(
660            "INSERT INTO device (user_id, device_id, data) \
661             VALUES (?1, ?2, ?3)
662             ON CONFLICT (user_id, device_id) DO UPDATE SET data = ?3",
663            (user_id, device_id, data),
664        )?;
665        Ok(())
666    }
667
668    fn delete_device(&self, user_id: &[u8], device_id: &[u8]) -> rusqlite::Result<()> {
669        self.execute(
670            "DELETE FROM device WHERE user_id = ? AND device_id = ?",
671            (user_id, device_id),
672        )?;
673        Ok(())
674    }
675
676    fn set_identity(&self, user_id: &[u8], data: &[u8]) -> rusqlite::Result<()> {
677        self.execute(
678            "INSERT INTO identity (user_id, data) \
679             VALUES (?1, ?2)
680             ON CONFLICT (user_id) DO UPDATE SET data = ?2",
681            (user_id, data),
682        )?;
683        Ok(())
684    }
685
686    fn add_olm_hash(&self, data: &[u8]) -> rusqlite::Result<()> {
687        self.execute("INSERT INTO olm_hash (data) VALUES (?) ON CONFLICT DO NOTHING", (data,))?;
688        Ok(())
689    }
690
691    fn set_key_request(
692        &self,
693        request_id: &[u8],
694        sent_out: bool,
695        data: &[u8],
696        info: &[u8],
697    ) -> rusqlite::Result<()> {
698        // The first `ON CONFLICT` cause will update a request if we try to save
699        // it again. The second `ON CONFLICT` will replace an old request for
700        // the same key/secret with the new request.
701        self.execute(
702            "INSERT INTO key_requests (request_id, sent_out, data, info)
703            VALUES (?1, ?2, ?3, ?4)
704            ON CONFLICT (request_id) DO UPDATE SET sent_out = ?2, data = ?3, info = ?4
705            ON CONFLICT (info) DO UPDATE SET request_id = ?1, sent_out = ?2, data = ?3",
706            (request_id, sent_out, data, info),
707        )?;
708        Ok(())
709    }
710
711    fn set_direct_withheld(
712        &self,
713        session_id: &[u8],
714        room_id: &[u8],
715        data: &[u8],
716    ) -> rusqlite::Result<()> {
717        self.execute(
718            "INSERT INTO direct_withheld_info (session_id, room_id, data)
719            VALUES (?1, ?2, ?3)
720            ON CONFLICT (session_id) DO UPDATE SET room_id = ?2, data = ?3",
721            (session_id, room_id, data),
722        )?;
723        Ok(())
724    }
725
726    fn set_room_settings(&self, room_id: &[u8], data: &[u8]) -> rusqlite::Result<()> {
727        self.execute(
728            "INSERT INTO room_settings (room_id, data)
729            VALUES (?1, ?2)
730            ON CONFLICT (room_id) DO UPDATE SET data = ?2",
731            (room_id, data),
732        )?;
733        Ok(())
734    }
735
736    fn set_secret(&self, secret_name: &[u8], secret: &[u8]) -> rusqlite::Result<()> {
737        // Ignore duplicate values, since we may get set the same secret
738        // multiple times.
739        self.execute(
740            "INSERT OR IGNORE INTO secrets_inbox (secret_name, secret)
741            VALUES (?1, ?2)",
742            (secret_name, secret),
743        )?;
744
745        Ok(())
746    }
747
748    fn set_received_room_key_bundle(
749        &self,
750        room_id: &[u8],
751        sender_user_id: &[u8],
752        data: &[u8],
753    ) -> rusqlite::Result<()> {
754        self.execute(
755            "INSERT INTO received_room_key_bundle(room_id, sender_user_id, bundle_data)
756            VALUES (?1, ?2, ?3)
757            ON CONFLICT (room_id, sender_user_id) DO UPDATE SET bundle_data = ?3",
758            (room_id, sender_user_id, data),
759        )?;
760        Ok(())
761    }
762
763    fn set_room_pending_key_bundle(
764        &self,
765        room_id: &[u8],
766        data: Option<&[u8]>,
767    ) -> rusqlite::Result<()> {
768        if let Some(data) = data {
769            self.execute(
770                "INSERT INTO rooms_pending_key_bundle (room_id, data)
771                 VALUES (?1, ?2)
772                 ON CONFLICT (room_id) DO UPDATE SET data = ?2",
773                (room_id, data),
774            )?;
775        } else {
776            self.execute("DELETE FROM rooms_pending_key_bundle WHERE room_id = ?1", (room_id,))?;
777        }
778        Ok(())
779    }
780
781    fn set_has_downloaded_all_room_keys(&self, room_id: &[u8]) -> rusqlite::Result<()> {
782        self.execute(
783            "INSERT INTO room_key_backups_fully_downloaded(room_id)
784             VALUES (?1)
785             ON CONFLICT(room_id) DO NOTHING",
786            (room_id,),
787        )?;
788        Ok(())
789    }
790}
791
792#[async_trait]
793trait SqliteObjectCryptoStoreExt: SqliteAsyncConnExt {
794    async fn get_sessions_for_sender_key(&self, sender_key: Key) -> Result<Vec<Vec<u8>>> {
795        Ok(self
796            .prepare("SELECT data FROM session WHERE sender_key = ?", |mut stmt| {
797                stmt.query((sender_key,))?.mapped(|row| row.get(0)).collect()
798            })
799            .await?)
800    }
801
802    async fn get_inbound_group_session(
803        &self,
804        session_id: Key,
805    ) -> Result<Option<(Vec<u8>, Vec<u8>, bool)>> {
806        Ok(self
807            .query_one(
808                "SELECT room_id, data, backed_up FROM inbound_group_session WHERE session_id = ?",
809                (session_id,),
810                |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
811            )
812            .await
813            .optional()?)
814    }
815
816    async fn get_inbound_group_sessions(&self) -> Result<Vec<(Vec<u8>, bool)>> {
817        Ok(self
818            .prepare("SELECT data, backed_up FROM inbound_group_session", |mut stmt| {
819                stmt.query(())?.mapped(|row| Ok((row.get(0)?, row.get(1)?))).collect()
820            })
821            .await?)
822    }
823
824    async fn get_inbound_group_session_counts(
825        &self,
826        _backup_version: Option<&str>,
827    ) -> Result<RoomKeyCounts> {
828        let total = self
829            .query_one("SELECT count(*) FROM inbound_group_session", (), |row| row.get(0))
830            .await?;
831        let backed_up = self
832            .query_one(
833                "SELECT count(*) FROM inbound_group_session WHERE backed_up = TRUE",
834                (),
835                |row| row.get(0),
836            )
837            .await?;
838        Ok(RoomKeyCounts { total, backed_up })
839    }
840
841    async fn get_inbound_group_sessions_by_room_id(
842        &self,
843        room_id: Key,
844    ) -> Result<Vec<(Vec<u8>, bool)>> {
845        Ok(self
846            .prepare(
847                "SELECT data, backed_up FROM inbound_group_session WHERE room_id = :room_id",
848                move |mut stmt| {
849                    stmt.query(named_params! {
850                        ":room_id": room_id,
851                    })?
852                    .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
853                    .collect()
854                },
855            )
856            .await?)
857    }
858
859    async fn get_inbound_group_sessions_for_device_batch(
860        &self,
861        sender_key: Key,
862        sender_data_type: SenderDataType,
863        after_session_id: Option<Key>,
864        limit: usize,
865    ) -> Result<Vec<(Vec<u8>, bool)>> {
866        Ok(self
867            .prepare(
868                "
869                SELECT data, backed_up
870                FROM inbound_group_session
871                WHERE sender_key = :sender_key
872                    AND sender_data_type = :sender_data_type
873                    AND session_id > :after_session_id
874                ORDER BY session_id
875                LIMIT :limit
876                ",
877                move |mut stmt| {
878                    let sender_data_type = sender_data_type as u8;
879
880                    // If we are not provided with an `after_session_id`, use a
881                    // key which will sort before all real keys: the empty
882                    // string.
883                    let after_session_id = after_session_id.unwrap_or(Key::Plain(Vec::new()));
884
885                    stmt.query(named_params! {
886                        ":sender_key": sender_key,
887                        ":sender_data_type": sender_data_type,
888                        ":after_session_id": after_session_id,
889                        ":limit": limit,
890                    })?
891                    .mapped(|row| Ok((row.get(0)?, row.get(1)?)))
892                    .collect()
893                },
894            )
895            .await?)
896    }
897
898    async fn get_inbound_group_sessions_for_backup(&self, limit: usize) -> Result<Vec<Vec<u8>>> {
899        Ok(self
900            .prepare(
901                "SELECT data FROM inbound_group_session WHERE backed_up = FALSE LIMIT ?",
902                move |mut stmt| stmt.query((limit,))?.mapped(|row| row.get(0)).collect(),
903            )
904            .await?)
905    }
906
907    async fn mark_inbound_group_sessions_as_backed_up(&self, session_ids: Vec<Key>) -> Result<()> {
908        if session_ids.is_empty() {
909            // We are not expecting to be called with an empty list of sessions
910            warn!("No sessions to mark as backed up!");
911            return Ok(());
912        }
913
914        self.chunk_large_query_over(session_ids, None, move |txn, session_ids| {
915            // Safety: host parameters are not generated using any user input
916            // except the number of session IDs, so it is safe from injection.
917            let query = format!(
918                "UPDATE inbound_group_session SET backed_up = TRUE where session_id IN ({})",
919                session_ids.host_parameters()
920            );
921            txn.prepare(&query)?.execute(params_from_iter(session_ids))?;
922            Ok(Vec::<()>::new())
923        })
924        .await?;
925
926        Ok(())
927    }
928
929    async fn reset_inbound_group_session_backup_state(&self) -> Result<()> {
930        self.execute("UPDATE inbound_group_session SET backed_up = FALSE", ()).await?;
931        Ok(())
932    }
933
934    async fn get_outbound_group_session(&self, room_id: Key) -> Result<Option<Vec<u8>>> {
935        Ok(self
936            .query_one(
937                "SELECT data FROM outbound_group_session WHERE room_id = ?",
938                (room_id,),
939                |row| row.get(0),
940            )
941            .await
942            .optional()?)
943    }
944
945    async fn get_device(&self, user_id: Key, device_id: Key) -> Result<Option<Vec<u8>>> {
946        Ok(self
947            .query_one(
948                "SELECT data FROM device WHERE user_id = ? AND device_id = ?",
949                (user_id, device_id),
950                |row| row.get(0),
951            )
952            .await
953            .optional()?)
954    }
955
956    async fn get_user_devices(&self, user_id: Key) -> Result<Vec<Vec<u8>>> {
957        Ok(self
958            .prepare("SELECT data FROM device WHERE user_id = ?", |mut stmt| {
959                stmt.query((user_id,))?.mapped(|row| row.get(0)).collect()
960            })
961            .await?)
962    }
963
964    async fn get_user_identity(&self, user_id: Key) -> Result<Option<Vec<u8>>> {
965        Ok(self
966            .query_one("SELECT data FROM identity WHERE user_id = ?", (user_id,), |row| row.get(0))
967            .await
968            .optional()?)
969    }
970
971    async fn has_olm_hash(&self, data: Vec<u8>) -> Result<bool> {
972        Ok(self
973            .query_one("SELECT count(*) FROM olm_hash WHERE data = ?", (data,), |row| {
974                row.get::<_, i32>(0)
975            })
976            .await?
977            > 0)
978    }
979
980    async fn get_tracked_users(&self) -> Result<Vec<Vec<u8>>> {
981        Ok(self
982            .prepare("SELECT data FROM tracked_user", |mut stmt| {
983                stmt.query(())?.mapped(|row| row.get(0)).collect()
984            })
985            .await?)
986    }
987
988    async fn add_tracked_users(&self, users: Vec<(Key, Vec<u8>)>) -> Result<()> {
989        Ok(self
990            .prepare(
991                "INSERT INTO tracked_user (user_id, data) \
992                 VALUES (?1, ?2) \
993                 ON CONFLICT (user_id) DO UPDATE SET data = ?2",
994                |mut stmt| {
995                    for (user_id, data) in users {
996                        stmt.execute((user_id, data))?;
997                    }
998
999                    Ok(())
1000                },
1001            )
1002            .await?)
1003    }
1004
1005    async fn get_outgoing_secret_request(
1006        &self,
1007        request_id: Key,
1008    ) -> Result<Option<(Vec<u8>, bool)>> {
1009        Ok(self
1010            .query_one(
1011                "SELECT data, sent_out FROM key_requests WHERE request_id = ?",
1012                (request_id,),
1013                |row| Ok((row.get(0)?, row.get(1)?)),
1014            )
1015            .await
1016            .optional()?)
1017    }
1018
1019    async fn get_secret_request_by_info(&self, info: Key) -> Result<Option<(Vec<u8>, bool)>> {
1020        Ok(self
1021            .query_one("SELECT data, sent_out FROM key_requests WHERE info = ?", (info,), |row| {
1022                Ok((row.get(0)?, row.get(1)?))
1023            })
1024            .await
1025            .optional()?)
1026    }
1027
1028    async fn get_unsent_secret_requests(&self) -> Result<Vec<Vec<u8>>> {
1029        Ok(self
1030            .prepare("SELECT data FROM key_requests WHERE sent_out = FALSE", |mut stmt| {
1031                stmt.query(())?.mapped(|row| row.get(0)).collect()
1032            })
1033            .await?)
1034    }
1035
1036    async fn delete_key_request(&self, request_id: Key) -> Result<()> {
1037        self.execute("DELETE FROM key_requests WHERE request_id = ?", (request_id,)).await?;
1038        Ok(())
1039    }
1040
1041    async fn get_secrets_from_inbox(&self, secret_name: Key) -> Result<Vec<Vec<u8>>> {
1042        Ok(self
1043            .prepare("SELECT secret FROM secrets_inbox WHERE secret_name = ?", |mut stmt| {
1044                stmt.query((secret_name,))?.mapped(|row| row.get(0)).collect()
1045            })
1046            .await?)
1047    }
1048
1049    async fn delete_secrets_from_inbox(&self, secret_name: Key) -> Result<()> {
1050        self.execute("DELETE FROM secrets_inbox WHERE secret_name = ?", (secret_name,)).await?;
1051        Ok(())
1052    }
1053
1054    async fn get_direct_withheld_info(
1055        &self,
1056        session_id: Key,
1057        room_id: Key,
1058    ) -> Result<Option<Vec<u8>>> {
1059        Ok(self
1060            .query_one(
1061                "SELECT data FROM direct_withheld_info WHERE session_id = ?1 AND room_id = ?2",
1062                (session_id, room_id),
1063                |row| row.get(0),
1064            )
1065            .await
1066            .optional()?)
1067    }
1068
1069    async fn get_withheld_sessions_by_room_id(&self, room_id: Key) -> Result<Vec<Vec<u8>>> {
1070        Ok(self
1071            .prepare("SELECT data FROM direct_withheld_info WHERE room_id = ?1", |mut stmt| {
1072                stmt.query((room_id,))?.mapped(|row| row.get(0)).collect()
1073            })
1074            .await?)
1075    }
1076
1077    async fn get_room_settings(&self, room_id: Key) -> Result<Option<Vec<u8>>> {
1078        Ok(self
1079            .query_one("SELECT data FROM room_settings WHERE room_id = ?", (room_id,), |row| {
1080                row.get(0)
1081            })
1082            .await
1083            .optional()?)
1084    }
1085
1086    async fn get_received_room_key_bundle(
1087        &self,
1088        room_id: Key,
1089        sender_user: Key,
1090    ) -> Result<Option<Vec<u8>>> {
1091        Ok(self
1092            .query_one(
1093                "SELECT bundle_data FROM received_room_key_bundle WHERE room_id = ? AND sender_user_id = ?",
1094                (room_id, sender_user),
1095                |row| { row.get(0) },
1096            )
1097            .await
1098            .optional()?)
1099    }
1100
1101    async fn get_room_pending_key_bundle(&self, room_id: Key) -> Result<Option<Vec<u8>>> {
1102        Ok(self
1103            .query_one(
1104                "SELECT data FROM rooms_pending_key_bundle WHERE room_id = ?",
1105                (room_id,),
1106                |row| row.get(0),
1107            )
1108            .await
1109            .optional()?)
1110    }
1111
1112    async fn get_all_rooms_pending_key_bundle(&self) -> Result<Vec<Vec<u8>>> {
1113        Ok(self
1114            .query_many("SELECT data FROM rooms_pending_key_bundle", (), |row| row.get(0))
1115            .await?)
1116    }
1117
1118    async fn has_downloaded_all_room_keys(&self, room_id: Key) -> Result<bool> {
1119        Ok(self
1120            .query_row(
1121                "SELECT EXISTS (SELECT 1 FROM room_key_backups_fully_downloaded WHERE room_id = ?)",
1122                (room_id,),
1123                |row| row.get(0),
1124            )
1125            .await?)
1126    }
1127}
1128
1129#[async_trait]
1130impl SqliteObjectCryptoStoreExt for SqliteAsyncConn {}
1131
1132#[async_trait]
1133impl CryptoStore for SqliteCryptoStore {
1134    type Error = Error;
1135
1136    async fn load_account(&self) -> Result<Option<Account>> {
1137        let conn = self.read().await?;
1138        if let Some(pickle) = conn.get_kv("account").await? {
1139            let pickle = self.deserialize_value(&pickle)?;
1140
1141            let account = Account::from_pickle(pickle).map_err(|_| Error::Unpickle)?;
1142
1143            *self.static_account.write().unwrap() = Some(account.static_data().clone());
1144
1145            Ok(Some(account))
1146        } else {
1147            Ok(None)
1148        }
1149    }
1150
1151    async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
1152        let conn = self.read().await?;
1153        if let Some(i) = conn.get_kv("identity").await? {
1154            let pickle = self.deserialize_value(&i)?;
1155            Ok(Some(PrivateCrossSigningIdentity::from_pickle(pickle).map_err(|_| Error::Unpickle)?))
1156        } else {
1157            Ok(None)
1158        }
1159    }
1160
1161    async fn save_pending_changes(&self, changes: PendingChanges) -> Result<()> {
1162        // Serialize calls to `save_pending_changes`; there are multiple await
1163        // points below, and we're pickling data as we go, so we don't want to
1164        // invalidate data we've previously read and overwrite it in the store.
1165        // TODO: #2000 should make this lock go away, or change its shape.
1166        let _guard = self.save_changes_lock.lock().await;
1167
1168        let pickled_account = if let Some(account) = changes.account {
1169            *self.static_account.write().unwrap() = Some(account.static_data().clone());
1170            Some(account.pickle())
1171        } else {
1172            None
1173        };
1174
1175        let this = self.clone();
1176        self.write()
1177            .await?
1178            .with_transaction(move |txn| {
1179                if let Some(pickled_account) = pickled_account {
1180                    let serialized_account = this.serialize_value(&pickled_account)?;
1181                    txn.set_kv("account", &serialized_account)?;
1182                }
1183
1184                Ok::<_, Error>(())
1185            })
1186            .await?;
1187
1188        Ok(())
1189    }
1190
1191    async fn save_changes(&self, changes: Changes) -> Result<()> {
1192        // Serialize calls to `save_changes`; there are multiple await points
1193        // below, and we're pickling data as we go, so we don't want to
1194        // invalidate data we've previously read and overwrite it in the store.
1195        // TODO: #2000 should make this lock go away, or change its shape.
1196        let _guard = self.save_changes_lock.lock().await;
1197
1198        let pickled_private_identity =
1199            if let Some(i) = changes.private_identity { Some(i.pickle().await) } else { None };
1200
1201        let mut session_changes = Vec::new();
1202
1203        for session in changes.sessions {
1204            let session_id = self.encode_key("session", session.session_id());
1205            let sender_key = self.encode_key("session", session.sender_key().to_base64());
1206            let pickle = session.pickle().await;
1207            session_changes.push((session_id, sender_key, pickle));
1208        }
1209
1210        let mut inbound_session_changes = Vec::new();
1211        for session in changes.inbound_group_sessions {
1212            let room_id = self.encode_key("inbound_group_session", session.room_id().as_bytes());
1213            let session_id = self.encode_key("inbound_group_session", session.session_id());
1214            let pickle = session.pickle().await;
1215            let sender_key =
1216                self.encode_key("inbound_group_session", session.sender_key().to_base64());
1217            inbound_session_changes.push((room_id, session_id, pickle, sender_key));
1218        }
1219
1220        let mut outbound_session_changes = Vec::new();
1221        for session in changes.outbound_group_sessions {
1222            let room_id = self.encode_key("outbound_group_session", session.room_id().as_bytes());
1223            let pickle = session.pickle().await;
1224            outbound_session_changes.push((room_id, pickle));
1225        }
1226
1227        let this = self.clone();
1228        self.write()
1229            .await?
1230            .with_transaction(move |txn| {
1231                if let Some(pickled_private_identity) = &pickled_private_identity {
1232                    let serialized_private_identity =
1233                        this.serialize_value(pickled_private_identity)?;
1234                    txn.set_kv("identity", &serialized_private_identity)?;
1235                }
1236
1237                if let Some(token) = &changes.next_batch_token {
1238                    let serialized_token = this.serialize_value(token)?;
1239                    txn.set_kv("next_batch_token", &serialized_token)?;
1240                }
1241
1242                if let Some(decryption_key) = &changes.backup_decryption_key {
1243                    let serialized_decryption_key = this.serialize_value(decryption_key)?;
1244                    txn.set_kv("recovery_key_v1", &serialized_decryption_key)?;
1245                }
1246
1247                if let Some(backup_version) = &changes.backup_version {
1248                    let serialized_backup_version = this.serialize_value(backup_version)?;
1249                    txn.set_kv("backup_version_v1", &serialized_backup_version)?;
1250                }
1251
1252                if let Some(pickle_key) = &changes.dehydrated_device_pickle_key {
1253                    let serialized_pickle_key = this.serialize_value(pickle_key)?;
1254                    txn.set_kv(DEHYDRATED_DEVICE_PICKLE_KEY, &serialized_pickle_key)?;
1255                }
1256
1257                for device in changes.devices.new.iter().chain(&changes.devices.changed) {
1258                    let user_id = this.encode_key("device", device.user_id().as_bytes());
1259                    let device_id = this.encode_key("device", device.device_id().as_bytes());
1260                    let data = this.serialize_value(&device)?;
1261                    txn.set_device(&user_id, &device_id, &data)?;
1262                }
1263
1264                for device in &changes.devices.deleted {
1265                    let user_id = this.encode_key("device", device.user_id().as_bytes());
1266                    let device_id = this.encode_key("device", device.device_id().as_bytes());
1267                    txn.delete_device(&user_id, &device_id)?;
1268                }
1269
1270                for identity in changes.identities.changed.iter().chain(&changes.identities.new) {
1271                    let user_id = this.encode_key("identity", identity.user_id().as_bytes());
1272                    let data = this.serialize_value(&identity)?;
1273                    txn.set_identity(&user_id, &data)?;
1274                }
1275
1276                for (session_id, sender_key, pickle) in &session_changes {
1277                    let serialized_session = this.serialize_value(&pickle)?;
1278                    txn.set_session(session_id, sender_key, &serialized_session)?;
1279                }
1280
1281                for (room_id, session_id, pickle, sender_key) in &inbound_session_changes {
1282                    let serialized_session = this.serialize_value(&pickle)?;
1283                    txn.set_inbound_group_session(
1284                        room_id,
1285                        session_id,
1286                        &serialized_session,
1287                        pickle.backed_up,
1288                        Some(sender_key),
1289                        Some(pickle.sender_data.to_type() as u8),
1290                    )?;
1291                }
1292
1293                for (room_id, pickle) in &outbound_session_changes {
1294                    let serialized_session = this.serialize_json(&pickle)?;
1295                    txn.set_outbound_group_session(room_id, &serialized_session)?;
1296                }
1297
1298                for hash in &changes.message_hashes {
1299                    let hash = rmp_serde::to_vec(hash)?;
1300                    txn.add_olm_hash(&hash)?;
1301                }
1302
1303                for request in changes.key_requests {
1304                    let request_id = this.encode_key("key_requests", request.request_id.as_bytes());
1305                    let serialized_request = this.serialize_value(&request)?;
1306                    let serialized_info = this.encode_key("key_requests", request.info.as_key());
1307                    txn.set_key_request(
1308                        &request_id,
1309                        request.sent_out,
1310                        &serialized_request,
1311                        &serialized_info,
1312                    )?;
1313                }
1314
1315                for (room_id, data) in changes.withheld_session_info {
1316                    for (session_id, event) in data {
1317                        let session_id = this.encode_key("direct_withheld_info", session_id);
1318                        let room_id = this.encode_key("direct_withheld_info", &room_id);
1319                        let serialized_info = this.serialize_json(&event)?;
1320                        txn.set_direct_withheld(&session_id, &room_id, &serialized_info)?;
1321                    }
1322                }
1323
1324                for (room_id, settings) in changes.room_settings {
1325                    let room_id = this.encode_key("room_settings", room_id.as_bytes());
1326                    let value = this.serialize_value(&settings)?;
1327                    txn.set_room_settings(&room_id, &value)?;
1328                }
1329
1330                for secret in changes.secrets {
1331                    let secret_name =
1332                        this.encode_key("secrets_inbox", secret.secret_name.to_string());
1333                    let value = this.serialize_json(secret.secret.deref())?;
1334                    txn.set_secret(&secret_name, &value)?;
1335                }
1336
1337                for bundle in changes.received_room_key_bundles {
1338                    let room_id =
1339                        this.encode_key("received_room_key_bundle", &bundle.bundle_data.room_id);
1340                    let user_id = this.encode_key("received_room_key_bundle", &bundle.sender_user);
1341                    let value = this.serialize_value(&bundle)?;
1342                    txn.set_received_room_key_bundle(&room_id, &user_id, &value)?;
1343                }
1344
1345                for room in changes.room_key_backups_fully_downloaded {
1346                    let room_id = this.encode_key("room_key_backups_fully_downloaded", &room);
1347                    txn.set_has_downloaded_all_room_keys(&room_id)?;
1348                }
1349
1350                for (room, details) in changes.rooms_pending_key_bundle {
1351                    let room_id = this.encode_key("rooms_pending_key_bundle", &room);
1352                    let value = details.as_ref().map(|d| this.serialize_value(d)).transpose()?;
1353                    txn.set_room_pending_key_bundle(&room_id, value.as_deref())?;
1354                }
1355
1356                Ok::<_, Error>(())
1357            })
1358            .await?;
1359
1360        Ok(())
1361    }
1362
1363    async fn save_inbound_group_sessions(
1364        &self,
1365        sessions: Vec<InboundGroupSession>,
1366        backed_up_to_version: Option<&str>,
1367    ) -> matrix_sdk_crypto::store::Result<(), Self::Error> {
1368        // Sanity-check that the data in the sessions corresponds to
1369        // backed_up_version
1370        sessions.iter().for_each(|s| {
1371            let backed_up = s.backed_up();
1372            if backed_up != backed_up_to_version.is_some() {
1373                warn!(
1374                    backed_up,
1375                    backed_up_to_version,
1376                    "Session backed-up flag does not correspond to backup version setting",
1377                );
1378            }
1379        });
1380
1381        // Currently, this store doesn't save the backup version separately, so
1382        // this just delegates to save_changes.
1383        self.save_changes(Changes { inbound_group_sessions: sessions, ..Changes::default() }).await
1384    }
1385
1386    async fn get_sessions(&self, sender_key: &str) -> Result<Option<Vec<Session>>> {
1387        let device_keys = self.get_own_device().await?.as_device_keys().clone();
1388
1389        let sessions: Vec<_> = self
1390            .read()
1391            .await?
1392            .get_sessions_for_sender_key(self.encode_key("session", sender_key.as_bytes()))
1393            .await?
1394            .into_iter()
1395            .map(|bytes| {
1396                let pickle = self.deserialize_value(&bytes)?;
1397                Session::from_pickle(device_keys.clone(), pickle).map_err(|_| Error::AccountUnset)
1398            })
1399            .collect::<Result<_>>()?;
1400
1401        if sessions.is_empty() { Ok(None) } else { Ok(Some(sessions)) }
1402    }
1403
1404    #[instrument(skip(self))]
1405    async fn get_inbound_group_session(
1406        &self,
1407        room_id: &RoomId,
1408        session_id: &str,
1409    ) -> Result<Option<InboundGroupSession>> {
1410        let session_id = self.encode_key("inbound_group_session", session_id);
1411        let Some((room_id_from_db, value, backed_up)) =
1412            self.read().await?.get_inbound_group_session(session_id).await?
1413        else {
1414            return Ok(None);
1415        };
1416
1417        let room_id = self.encode_key("inbound_group_session", room_id.as_bytes());
1418        if *room_id != room_id_from_db {
1419            warn!("expected room_id for session_id doesn't match what's in the DB");
1420            return Ok(None);
1421        }
1422
1423        Ok(Some(self.deserialize_and_unpickle_inbound_group_session(value, backed_up)?))
1424    }
1425
1426    async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>> {
1427        self.read()
1428            .await?
1429            .get_inbound_group_sessions()
1430            .await?
1431            .into_iter()
1432            .map(|(value, backed_up)| {
1433                self.deserialize_and_unpickle_inbound_group_session(value, backed_up)
1434            })
1435            .collect()
1436    }
1437
1438    async fn get_inbound_group_sessions_by_room_id(
1439        &self,
1440        room_id: &RoomId,
1441    ) -> Result<Vec<InboundGroupSession>> {
1442        let room_id = self.encode_key("inbound_group_session", room_id.as_bytes());
1443        self.read()
1444            .await?
1445            .get_inbound_group_sessions_by_room_id(room_id)
1446            .await?
1447            .into_iter()
1448            .map(|(value, backed_up)| {
1449                self.deserialize_and_unpickle_inbound_group_session(value, backed_up)
1450            })
1451            .collect()
1452    }
1453
1454    async fn get_inbound_group_sessions_for_device_batch(
1455        &self,
1456        sender_key: Curve25519PublicKey,
1457        sender_data_type: SenderDataType,
1458        after_session_id: Option<String>,
1459        limit: usize,
1460    ) -> Result<Vec<InboundGroupSession>, Self::Error> {
1461        let after_session_id =
1462            after_session_id.map(|session_id| self.encode_key("inbound_group_session", session_id));
1463        let sender_key = self.encode_key("inbound_group_session", sender_key.to_base64());
1464
1465        self.read()
1466            .await?
1467            .get_inbound_group_sessions_for_device_batch(
1468                sender_key,
1469                sender_data_type,
1470                after_session_id,
1471                limit,
1472            )
1473            .await?
1474            .into_iter()
1475            .map(|(value, backed_up)| {
1476                self.deserialize_and_unpickle_inbound_group_session(value, backed_up)
1477            })
1478            .collect()
1479    }
1480
1481    async fn inbound_group_session_counts(
1482        &self,
1483        backup_version: Option<&str>,
1484    ) -> Result<RoomKeyCounts> {
1485        Ok(self.read().await?.get_inbound_group_session_counts(backup_version).await?)
1486    }
1487
1488    async fn inbound_group_sessions_for_backup(
1489        &self,
1490        _backup_version: &str,
1491        limit: usize,
1492    ) -> Result<Vec<InboundGroupSession>> {
1493        self.read()
1494            .await?
1495            .get_inbound_group_sessions_for_backup(limit)
1496            .await?
1497            .into_iter()
1498            .map(|value| self.deserialize_and_unpickle_inbound_group_session(value, false))
1499            .collect()
1500    }
1501
1502    async fn mark_inbound_group_sessions_as_backed_up(
1503        &self,
1504        _backup_version: &str,
1505        session_ids: &[(&RoomId, &str)],
1506    ) -> Result<()> {
1507        Ok(self
1508            .write()
1509            .await?
1510            .mark_inbound_group_sessions_as_backed_up(
1511                session_ids
1512                    .iter()
1513                    .map(|(_, s)| self.encode_key("inbound_group_session", s))
1514                    .collect(),
1515            )
1516            .await?)
1517    }
1518
1519    async fn reset_backup_state(&self) -> Result<()> {
1520        Ok(self.write().await?.reset_inbound_group_session_backup_state().await?)
1521    }
1522
1523    async fn load_backup_keys(&self) -> Result<BackupKeys> {
1524        let conn = self.read().await?;
1525
1526        let backup_version = conn
1527            .get_kv("backup_version_v1")
1528            .await?
1529            .map(|value| self.deserialize_value(&value))
1530            .transpose()?;
1531
1532        let decryption_key = conn
1533            .get_kv("recovery_key_v1")
1534            .await?
1535            .map(|value| self.deserialize_value(&value))
1536            .transpose()?;
1537
1538        Ok(BackupKeys { backup_version, decryption_key })
1539    }
1540
1541    async fn load_dehydrated_device_pickle_key(&self) -> Result<Option<DehydratedDeviceKey>> {
1542        let conn = self.read().await?;
1543
1544        conn.get_kv(DEHYDRATED_DEVICE_PICKLE_KEY)
1545            .await?
1546            .map(|value| self.deserialize_value(&value))
1547            .transpose()
1548    }
1549
1550    async fn delete_dehydrated_device_pickle_key(&self) -> Result<(), Self::Error> {
1551        Ok(self.write().await?.clear_kv(DEHYDRATED_DEVICE_PICKLE_KEY).await?)
1552    }
1553    async fn get_outbound_group_session(
1554        &self,
1555        room_id: &RoomId,
1556    ) -> Result<Option<OutboundGroupSession>> {
1557        let room_id = self.encode_key("outbound_group_session", room_id.as_bytes());
1558        let Some(value) = self.read().await?.get_outbound_group_session(room_id).await? else {
1559            return Ok(None);
1560        };
1561
1562        let account_info = self.get_static_account().ok_or(Error::AccountUnset)?;
1563
1564        let pickle = self.deserialize_json(&value)?;
1565        let session = OutboundGroupSession::from_pickle(
1566            account_info.device_id,
1567            account_info.identity_keys,
1568            pickle,
1569        )
1570        .map_err(|_| Error::Unpickle)?;
1571
1572        return Ok(Some(session));
1573    }
1574
1575    async fn load_tracked_users(&self) -> Result<Vec<TrackedUser>> {
1576        self.read()
1577            .await?
1578            .get_tracked_users()
1579            .await?
1580            .iter()
1581            .map(|value| self.deserialize_value(value))
1582            .collect()
1583    }
1584
1585    async fn save_tracked_users(&self, tracked_users: &[(&UserId, bool)]) -> Result<()> {
1586        let users: Vec<(Key, Vec<u8>)> = tracked_users
1587            .iter()
1588            .map(|(u, d)| {
1589                let user_id = self.encode_key("tracked_users", u.as_bytes());
1590                let data =
1591                    self.serialize_value(&TrackedUser { user_id: (*u).into(), dirty: *d })?;
1592                Ok((user_id, data))
1593            })
1594            .collect::<Result<_>>()?;
1595
1596        Ok(self.write().await?.add_tracked_users(users).await?)
1597    }
1598
1599    async fn get_device(
1600        &self,
1601        user_id: &UserId,
1602        device_id: &DeviceId,
1603    ) -> Result<Option<DeviceData>> {
1604        let user_id = self.encode_key("device", user_id.as_bytes());
1605        let device_id = self.encode_key("device", device_id.as_bytes());
1606        Ok(self
1607            .read()
1608            .await?
1609            .get_device(user_id, device_id)
1610            .await?
1611            .map(|value| self.deserialize_value(&value))
1612            .transpose()?)
1613    }
1614
1615    async fn get_user_devices(
1616        &self,
1617        user_id: &UserId,
1618    ) -> Result<HashMap<OwnedDeviceId, DeviceData>> {
1619        let user_id = self.encode_key("device", user_id.as_bytes());
1620        self.read()
1621            .await?
1622            .get_user_devices(user_id)
1623            .await?
1624            .into_iter()
1625            .map(|value| {
1626                let device: DeviceData = self.deserialize_value(&value)?;
1627                Ok((device.device_id().to_owned(), device))
1628            })
1629            .collect()
1630    }
1631
1632    async fn get_own_device(&self) -> Result<DeviceData> {
1633        let account_info = self.get_static_account().ok_or(Error::AccountUnset)?;
1634
1635        Ok(self
1636            .get_device(&account_info.user_id, &account_info.device_id)
1637            .await?
1638            .expect("We should be able to find our own device."))
1639    }
1640
1641    async fn get_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentityData>> {
1642        let user_id = self.encode_key("identity", user_id.as_bytes());
1643        Ok(self
1644            .read()
1645            .await?
1646            .get_user_identity(user_id)
1647            .await?
1648            .map(|value| self.deserialize_value(&value))
1649            .transpose()?)
1650    }
1651
1652    async fn is_message_known(
1653        &self,
1654        message_hash: &matrix_sdk_crypto::olm::OlmMessageHash,
1655    ) -> Result<bool> {
1656        let value = rmp_serde::to_vec(message_hash)?;
1657        Ok(self.read().await?.has_olm_hash(value).await?)
1658    }
1659
1660    async fn get_outgoing_secret_requests(
1661        &self,
1662        request_id: &TransactionId,
1663    ) -> Result<Option<GossipRequest>> {
1664        let request_id = self.encode_key("key_requests", request_id.as_bytes());
1665        Ok(self
1666            .read()
1667            .await?
1668            .get_outgoing_secret_request(request_id)
1669            .await?
1670            .map(|(value, sent_out)| self.deserialize_key_request(&value, sent_out))
1671            .transpose()?)
1672    }
1673
1674    async fn get_secret_request_by_info(
1675        &self,
1676        key_info: &SecretInfo,
1677    ) -> Result<Option<GossipRequest>> {
1678        let key_info = self.encode_key("key_requests", key_info.as_key());
1679        Ok(self
1680            .read()
1681            .await?
1682            .get_secret_request_by_info(key_info)
1683            .await?
1684            .map(|(value, sent_out)| self.deserialize_key_request(&value, sent_out))
1685            .transpose()?)
1686    }
1687
1688    async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>> {
1689        self.read()
1690            .await?
1691            .get_unsent_secret_requests()
1692            .await?
1693            .iter()
1694            .map(|value| {
1695                let request = self.deserialize_key_request(value, false)?;
1696                Ok(request)
1697            })
1698            .collect()
1699    }
1700
1701    async fn delete_outgoing_secret_requests(&self, request_id: &TransactionId) -> Result<()> {
1702        let request_id = self.encode_key("key_requests", request_id.as_bytes());
1703        Ok(self.write().await?.delete_key_request(request_id).await?)
1704    }
1705
1706    async fn get_secrets_from_inbox(
1707        &self,
1708        secret_name: &SecretName,
1709    ) -> Result<Vec<Zeroizing<String>>> {
1710        let secret_name = self.encode_key("secrets_inbox", secret_name.to_string());
1711
1712        self.read()
1713            .await?
1714            .get_secrets_from_inbox(secret_name)
1715            .await?
1716            .into_iter()
1717            .map(|value| self.deserialize_json(value.as_ref()).map(|value: String| value.into()))
1718            .collect()
1719    }
1720
1721    async fn delete_secrets_from_inbox(&self, secret_name: &SecretName) -> Result<()> {
1722        let secret_name = self.encode_key("secrets_inbox", secret_name.to_string());
1723        self.write().await?.delete_secrets_from_inbox(secret_name).await
1724    }
1725
1726    async fn get_withheld_info(
1727        &self,
1728        room_id: &RoomId,
1729        session_id: &str,
1730    ) -> Result<Option<RoomKeyWithheldEntry>> {
1731        let room_id = self.encode_key("direct_withheld_info", room_id);
1732        let session_id = self.encode_key("direct_withheld_info", session_id);
1733
1734        self.read()
1735            .await?
1736            .get_direct_withheld_info(session_id, room_id)
1737            .await?
1738            .map(|value| {
1739                let info = self.deserialize_json::<RoomKeyWithheldEntry>(&value)?;
1740                Ok(info)
1741            })
1742            .transpose()
1743    }
1744
1745    async fn get_withheld_sessions_by_room_id(
1746        &self,
1747        room_id: &RoomId,
1748    ) -> matrix_sdk_crypto::store::Result<Vec<RoomKeyWithheldEntry>, Self::Error> {
1749        let room_id = self.encode_key("direct_withheld_info", room_id);
1750
1751        self.read()
1752            .await?
1753            .get_withheld_sessions_by_room_id(room_id)
1754            .await?
1755            .into_iter()
1756            .map(|value| self.deserialize_json(&value))
1757            .collect()
1758    }
1759
1760    async fn get_room_settings(&self, room_id: &RoomId) -> Result<Option<RoomSettings>> {
1761        let room_id = self.encode_key("room_settings", room_id.as_bytes());
1762        let Some(value) = self.read().await?.get_room_settings(room_id).await? else {
1763            return Ok(None);
1764        };
1765
1766        let settings = self.deserialize_value(&value)?;
1767
1768        return Ok(Some(settings));
1769    }
1770
1771    async fn get_received_room_key_bundle_data(
1772        &self,
1773        room_id: &RoomId,
1774        user_id: &UserId,
1775    ) -> Result<Option<StoredRoomKeyBundleData>> {
1776        let room_id = self.encode_key("received_room_key_bundle", room_id);
1777        let user_id = self.encode_key("received_room_key_bundle", user_id);
1778        self.read()
1779            .await?
1780            .get_received_room_key_bundle(room_id, user_id)
1781            .await?
1782            .map(|value| self.deserialize_value(&value))
1783            .transpose()
1784    }
1785
1786    async fn has_downloaded_all_room_keys(&self, room_id: &RoomId) -> Result<bool> {
1787        let room_id = self.encode_key("room_key_backups_fully_downloaded", room_id);
1788        self.read().await?.has_downloaded_all_room_keys(room_id).await
1789    }
1790
1791    async fn get_pending_key_bundle_details_for_room(
1792        &self,
1793        room_id: &RoomId,
1794    ) -> Result<Option<RoomPendingKeyBundleDetails>> {
1795        let room_id = self.encode_key("rooms_pending_key_bundle", room_id.as_bytes());
1796        let Some(value) = self.read().await?.get_room_pending_key_bundle(room_id).await? else {
1797            return Ok(None);
1798        };
1799
1800        let details = self.deserialize_value(&value)?;
1801        Ok(Some(details))
1802    }
1803
1804    async fn get_all_rooms_pending_key_bundles(&self) -> Result<Vec<RoomPendingKeyBundleDetails>> {
1805        let details = self.read().await?.get_all_rooms_pending_key_bundle().await?;
1806        let room_ids = details
1807            .into_iter()
1808            .map(|value| self.deserialize_value(&value))
1809            .collect::<Result<_, _>>()?;
1810        Ok(room_ids)
1811    }
1812
1813    async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>> {
1814        let Some(serialized) = self.read().await?.get_kv(key).await? else {
1815            return Ok(None);
1816        };
1817        let value = if let Some(cipher) = &self.store_cipher {
1818            let encrypted = rmp_serde::from_slice(&serialized)?;
1819            cipher.decrypt_value_data(encrypted)?
1820        } else {
1821            serialized
1822        };
1823
1824        Ok(Some(value))
1825    }
1826
1827    async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<()> {
1828        let serialized = if let Some(cipher) = &self.store_cipher {
1829            let encrypted = cipher.encrypt_value_data(value)?;
1830            rmp_serde::to_vec_named(&encrypted)?
1831        } else {
1832            value
1833        };
1834
1835        self.write().await?.set_kv(key, serialized).await?;
1836        Ok(())
1837    }
1838
1839    async fn remove_custom_value(&self, key: &str) -> Result<()> {
1840        let key = key.to_owned();
1841        self.write()
1842            .await?
1843            .interact(move |conn| conn.execute("DELETE FROM kv WHERE key = ?1", (&key,)))
1844            .await
1845            .map_err(map_interact_err)
1846            .flatten()?;
1847        Ok(())
1848    }
1849
1850    #[instrument(skip(self))]
1851    async fn try_take_leased_lock(
1852        &self,
1853        lease_duration_ms: u32,
1854        key: &str,
1855        holder: &str,
1856    ) -> Result<Option<CrossProcessLockGeneration>> {
1857        let key = key.to_owned();
1858        let holder = holder.to_owned();
1859
1860        let now: u64 = MilliSecondsSinceUnixEpoch::now().get().into();
1861        let expiration = now + lease_duration_ms as u64;
1862
1863        // Learn about the `excluded` keyword in https://sqlite.org/lang_upsert.html.
1864        let generation = self
1865            .write()
1866            .await?
1867            .with_transaction(move |txn| {
1868                txn.query_one(
1869                    "INSERT INTO lease_locks (key, holder, expiration)
1870                    VALUES (?1, ?2, ?3)
1871                    ON CONFLICT (key)
1872                    DO
1873                        UPDATE SET
1874                            holder = excluded.holder,
1875                            expiration = excluded.expiration,
1876                            generation =
1877                                CASE holder
1878                                    WHEN excluded.holder THEN generation
1879                                    ELSE generation + 1
1880                                END
1881                        WHERE
1882                            holder = excluded.holder
1883                            OR expiration < ?4
1884                    RETURNING generation
1885                    ",
1886                    (key, holder, expiration, now),
1887                    |row| row.get(0),
1888                )
1889                .optional()
1890            })
1891            .await?;
1892
1893        Ok(generation)
1894    }
1895
1896    async fn next_batch_token(&self) -> Result<Option<String>, Self::Error> {
1897        let conn = self.read().await?;
1898        if let Some(token) = conn.get_kv("next_batch_token").await? {
1899            let maybe_token: Option<String> = self.deserialize_value(&token)?;
1900            Ok(maybe_token)
1901        } else {
1902            Ok(None)
1903        }
1904    }
1905
1906    async fn delete_next_batch_token(&self) -> Result<(), Self::Error> {
1907        self.remove_custom_value("next_batch_token").await
1908    }
1909
1910    async fn close(&self) -> Result<()> {
1911        connection::close_connections(&self.connections, "Crypto store").await;
1912        Ok(())
1913    }
1914
1915    async fn reopen(&self) -> Result<()> {
1916        connection::reopen_connections(
1917            &self.connections,
1918            self.db_path.clone(),
1919            self.pool_config,
1920            self.runtime_config,
1921        )
1922        .await?;
1923        Ok(())
1924    }
1925
1926    async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
1927        Ok(Some(self.read().await?.get_db_size().await?))
1928    }
1929}
1930
1931#[cfg(test)]
1932mod tests {
1933    use std::{path::Path, sync::LazyLock};
1934
1935    use matrix_sdk_common::deserialized_responses::WithheldCode;
1936    use matrix_sdk_crypto::{
1937        cryptostore_integration_tests, cryptostore_integration_tests_time, olm::SenderDataType,
1938        store::CryptoStore,
1939    };
1940    use matrix_sdk_test::async_test;
1941    use ruma::{device_id, room_id, user_id};
1942    use similar_asserts::assert_eq;
1943    use tempfile::{TempDir, tempdir};
1944    use tokio::fs;
1945
1946    use super::SqliteCryptoStore;
1947    use crate::SqliteStoreConfig;
1948
1949    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
1950
1951    struct TestDb {
1952        // Needs to be kept alive because the Drop implementation for TempDir
1953        // deletes the directory.
1954        _dir: TempDir,
1955        database: SqliteCryptoStore,
1956    }
1957
1958    fn copy_db(data_path: &str) -> TempDir {
1959        let db_name = super::DATABASE_NAME;
1960
1961        let manifest_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
1962        let database_path = manifest_path.join(data_path).join(db_name);
1963
1964        let tmpdir = tempdir().unwrap();
1965        let destination = tmpdir.path().join(db_name);
1966
1967        // Copy the test database to the tempdir so our test runs are
1968        // idempotent.
1969        std::fs::copy(&database_path, destination).unwrap();
1970
1971        tmpdir
1972    }
1973
1974    async fn get_test_db(data_path: &str, passphrase: Option<&str>) -> TestDb {
1975        let tmpdir = copy_db(data_path);
1976
1977        let database = SqliteCryptoStore::open(tmpdir.path(), passphrase)
1978            .await
1979            .expect("Can't open the test store");
1980
1981        TestDb { _dir: tmpdir, database }
1982    }
1983
1984    #[async_test]
1985    async fn test_pool_size() {
1986        let store_open_config =
1987            SqliteStoreConfig::new(TMP_DIR.path().join("test_pool_size")).pool_max_size(42);
1988
1989        let store = SqliteCryptoStore::open_with_config(&store_open_config).await.unwrap();
1990
1991        let guard = store.connections.lock().await;
1992        let conns = guard.as_ref().unwrap();
1993        assert_eq!(conns.pool.status().max_size, 42);
1994    }
1995
1996    /// Test that we didn't regress in our storage layer by loading data from a
1997    /// pre-filled database, or in other words use a test vector for this.
1998    #[async_test]
1999    async fn test_open_test_vector_store() {
2000        let TestDb { _dir: _, database } = get_test_db("testing/data/storage", None).await;
2001
2002        let account = database
2003            .load_account()
2004            .await
2005            .unwrap()
2006            .expect("The test database is prefilled with data, we should find an account");
2007
2008        let user_id = account.user_id();
2009        let device_id = account.device_id();
2010
2011        assert_eq!(
2012            user_id.as_str(),
2013            "@pjtest:synapse-oidc.element.dev",
2014            "The user ID should match to the one we expect."
2015        );
2016
2017        assert_eq!(
2018            device_id.as_str(),
2019            "v4TqgcuIH6",
2020            "The device ID should match to the one we expect."
2021        );
2022
2023        let device = database
2024            .get_device(user_id, device_id)
2025            .await
2026            .unwrap()
2027            .expect("Our own device should be found in the store.");
2028
2029        assert_eq!(device.device_id(), device_id);
2030        assert_eq!(device.user_id(), user_id);
2031
2032        assert_eq!(
2033            device.ed25519_key().expect("The device should have a Ed25519 key.").to_base64(),
2034            "+cxl1Gl3du5i7UJwfWnoRDdnafFF+xYdAiTYYhYLr8s"
2035        );
2036
2037        assert_eq!(
2038            device.curve25519_key().expect("The device should have a Curve25519 key.").to_base64(),
2039            "4SL9eEUlpyWSUvjljC5oMjknHQQJY7WZKo5S1KL/5VU"
2040        );
2041
2042        let identity = database
2043            .get_user_identity(user_id)
2044            .await
2045            .unwrap()
2046            .expect("The store should contain an identity.");
2047
2048        assert_eq!(identity.user_id(), user_id);
2049
2050        let identity = identity
2051            .own()
2052            .expect("The identity should be of the correct type, it should be our own identity.");
2053
2054        let master_key = identity
2055            .master_key()
2056            .get_first_key()
2057            .expect("Our own identity should have a master key");
2058
2059        assert_eq!(master_key.to_base64(), "iCUEtB1RwANeqRa5epDrblLk4mer/36sylwQ5hYY3oE");
2060    }
2061
2062    /// Test that we didn't regress in our storage layer by loading data from a
2063    /// pre-filled database, or in other words use a test vector for this.
2064    #[async_test]
2065    async fn test_open_test_vector_encrypted_store() {
2066        let TestDb { _dir: _, database } = get_test_db(
2067            "testing/data/storage/alice",
2068            Some(concat!(
2069                "/rCia2fYAJ+twCZ1Xm2mxFCYcmJdyzkdJjwtgXsziWpYS/UeNxnixuSieuwZXm+x1VsJHmWpl",
2070                "H+QIQBZpEGZtC9/S/l8xK+WOCesmET0o6yJ/KP73ofDtjBlnNpPwuHLKFpyTbyicpCgQ4UT+5E",
2071                "UBuJ08TY9Ujdf1D13k5kr5tSZUefDKKCuG1fCRqlU8ByRas1PMQsZxT2W8t7QgBrQiiGmhpo/O",
2072                "Ti4hfx97GOxncKcxTzppiYQNoHs/f15+XXQD7/oiCcqRIuUlXNsU6hRpFGmbYx2Pi1eyQViQCt",
2073                "B5dAEiSD0N8U81wXYnpynuTPtnL+hfnOJIn7Sy7mkERQeKg"
2074            )),
2075        )
2076        .await;
2077
2078        let account = database
2079            .load_account()
2080            .await
2081            .unwrap()
2082            .expect("The test database is prefilled with data, we should find an account");
2083
2084        let user_id = account.user_id();
2085        let device_id = account.device_id();
2086
2087        assert_eq!(
2088            user_id.as_str(),
2089            "@alice:localhost",
2090            "The user ID should match to the one we expect."
2091        );
2092
2093        assert_eq!(
2094            device_id.as_str(),
2095            "JVVORTHFXY",
2096            "The device ID should match to the one we expect."
2097        );
2098
2099        let tracked_users =
2100            database.load_tracked_users().await.expect("Should be tracking some users");
2101
2102        assert_eq!(tracked_users.len(), 6);
2103
2104        let known_users = vec![
2105            user_id!("@alice:localhost"),
2106            user_id!("@dehydration3:localhost"),
2107            user_id!("@eve:localhost"),
2108            user_id!("@bob:localhost"),
2109            user_id!("@malo:localhost"),
2110            user_id!("@carl:localhost"),
2111        ];
2112
2113        // load the identities
2114        for user_id in known_users {
2115            database.get_user_identity(user_id).await.expect("Should load this identity").unwrap();
2116        }
2117
2118        let carl_identity =
2119            database.get_user_identity(user_id!("@carl:localhost")).await.unwrap().unwrap();
2120
2121        assert_eq!(
2122            carl_identity.master_key().get_first_key().unwrap().to_base64(),
2123            "CdhKYYDeBDQveOioXEGWhTPCyzc63Irpar3CNyfun2Q"
2124        );
2125        assert!(!carl_identity.was_previously_verified());
2126
2127        let bob_identity =
2128            database.get_user_identity(user_id!("@bob:localhost")).await.unwrap().unwrap();
2129
2130        assert_eq!(
2131            bob_identity.master_key().get_first_key().unwrap().to_base64(),
2132            "COh2GYOJWSjem5QPRCaGp9iWV83IELG1IzLKW2S3pFY"
2133        );
2134        // Bob is verified so this flag should be set
2135        assert!(bob_identity.was_previously_verified());
2136
2137        let known_devices = vec![
2138            (device_id!("OPXQHCZSKW"), user_id!("@alice:localhost")),
2139            // a dehydrated one
2140            (
2141                device_id!("EvW+9IrGR10KVgVeZP25/KaPfx4R86FofVMcaz7VOho"),
2142                user_id!("@alice:localhost"),
2143            ),
2144            (device_id!("HEEFRFQENV"), user_id!("@alice:localhost")),
2145            (device_id!("JVVORTHFXY"), user_id!("@alice:localhost")),
2146            (device_id!("NQUWWSKKHS"), user_id!("@alice:localhost")),
2147            (device_id!("ORBLPFYCPG"), user_id!("@alice:localhost")),
2148            (device_id!("YXOWENSEGM"), user_id!("@dehydration3:localhost")),
2149            (device_id!("VXLFMYCHXC"), user_id!("@bob:localhost")),
2150            (device_id!("FDGDQAEWOW"), user_id!("@bob:localhost")),
2151            (device_id!("VXLFMYCHXC"), user_id!("@bob:localhost")),
2152            (device_id!("FDGDQAEWOW"), user_id!("@bob:localhost")),
2153            (device_id!("QKUKWJTTQC"), user_id!("@malo:localhost")),
2154            (device_id!("LOUXJECTFG"), user_id!("@malo:localhost")),
2155            (device_id!("MKKMAEVLPB"), user_id!("@carl:localhost")),
2156        ];
2157
2158        for (device_id, user_id) in known_devices {
2159            database.get_device(user_id, device_id).await.expect("Should load the device").unwrap();
2160        }
2161
2162        let known_sender_key_to_session_count = vec![
2163            ("FfYcYfDF4nWy+LHdK6CEpIMlFAQDORc30WUkghL06kM", 1),
2164            ("EvW+9IrGR10KVgVeZP25/KaPfx4R86FofVMcaz7VOho", 1),
2165            ("hAGsoA4a9M6wwEUX5Q1jux1i+tUngLi01n5AmhDoHTY", 1),
2166            ("aKqtSJymLzuoglWFwPGk1r/Vm2LE2hFESzXxn4RNjRM", 0),
2167            ("zHK1psCrgeMn0kaz8hcdvA3INyar9jg1yfrSp0p1pHo", 1),
2168            ("1QmBA316Wj5jIFRwNOti6N6Xh/vW0bsYCcR4uPfy8VQ", 1),
2169            ("g5ef2vZF3VXgSPyODIeXpyHIRkuthvLhGvd6uwYggWU", 1),
2170            ("o7hfupPd1VsNkRIvdlH6ujrEJFSKjFCGbxhAd31XxjI", 1),
2171            ("Z3RxKQLxY7xpP+ZdOGR2SiNE37SrvmRhW7GPu1UGdm8", 1),
2172            ("GDomaav8NiY3J+dNEeApJm+O0FooJ3IpVaIyJzCN4w4", 1),
2173            ("7m7fqkHyEr47V5s/KjaxtJMOr3pSHrrns2q2lWpAQi8", 0),
2174            ("9psAkPUIF8vNbWbnviX3PlwRcaeO53EHJdNtKpTY1X0", 0),
2175            ("mqanh+ztw5oRtpqYQgLGW864i6NY2zpoKMIlrcyC+Aw", 0),
2176            ("fJU/TJdbsv7tVbbpHw1Ke73ziElnM32cNhP2WIg4T10", 0),
2177            ("sUIeFeFcCZoa5IC6nJ6Vrbvztcyx09m8BBg57XKRClg", 1),
2178        ];
2179
2180        for (id, count) in known_sender_key_to_session_count {
2181            let olm_sessions =
2182                database.get_sessions(id).await.expect("Should have some olm sessions");
2183
2184            println!("### Session id: {id:?}");
2185            assert_eq!(olm_sessions.map_or(0, |v| v.len()), count);
2186        }
2187
2188        let inbound_group_sessions = database.get_inbound_group_sessions().await.unwrap();
2189        assert_eq!(inbound_group_sessions.len(), 15);
2190        let known_inbound_group_sessions = vec![
2191            (
2192                "5hNAxrLai3VI0LKBwfh3wLfksfBFWds0W1a5X5/vSXA",
2193                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2194            ),
2195            (
2196                "M6d2eU3y54gaYTbvGSlqa/xc1Az35l56Cp9sxzHWO4g",
2197                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2198            ),
2199            (
2200                "IrydwXkRk2N2AqUMIVmLL3oJgMq14R9KId0P/uSD100",
2201                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2202            ),
2203            (
2204                "Y74+l9jTo7N5UF+GQwdpgJGe4sn1+QtWITq7BxulHIE",
2205                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2206            ),
2207            (
2208                "HpJxQR57WbQGdY6w2Q+C16znVvbXGa+JvQdRoMpWbXg",
2209                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2210            ),
2211            (
2212                "Xetvi+ydFkZt8dpONGFbEusQb/Chc2V0XlLByZhsbgE",
2213                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2214            ),
2215            (
2216                "wv/WN/39akyerIXczTaIpjAuLnwgXKRtbXFSEHiJqxo",
2217                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2218            ),
2219            (
2220                "nA4gQwL//Cm8OdlyjABl/jChbPT/cP5V4Sd8iuE6H0s",
2221                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2222            ),
2223            (
2224                "bAAgqFeRDTjfEqL6Qf/c9mk55zoNDCSlboAIRd6b0hw",
2225                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2226            ),
2227            (
2228                "exPbsMMdGfAG2qmDdFtpAn+koVprfzS0Zip/RA9QRCE",
2229                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2230            ),
2231            (
2232                "h+om7oSw/ZV94fcKaoe8FGXJwQXWOfKQfzbGgNWQILI",
2233                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2234            ),
2235            (
2236                "ul3VXonpgk4lO2L3fEWubP/nxsTmLHqu5v8ZM9vHEcw",
2237                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2238            ),
2239            (
2240                "JXY15UxC3az2mwg8uX4qwgxfvCM4aygiIWMcdNiVQoc",
2241                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2242            ),
2243            (
2244                "OGB9lObr9kWUvha9tB5sMfOF/Mztk24JwQz/nwg3iFQ",
2245                room_id!("!OgRiTRMaUzLdpCeDBM:localhost"),
2246            ),
2247            (
2248                "SFkHcbxjUOYF7mUAYI/oEMDZFaXszQbCN6Jza7iemj0",
2249                room_id!("!OgRiTRMaUzLdpCeDBM:localhost"),
2250            ),
2251        ];
2252
2253        // ensure we can load them all
2254        for (session_id, room_id) in &known_inbound_group_sessions {
2255            database
2256                .get_inbound_group_session(room_id, session_id)
2257                .await
2258                .expect("Should be able to load inbound group session")
2259                .unwrap();
2260        }
2261
2262        let bob_sender_verified = database
2263            .get_inbound_group_session(
2264                room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"),
2265                "exPbsMMdGfAG2qmDdFtpAn+koVprfzS0Zip/RA9QRCE",
2266            )
2267            .await
2268            .unwrap()
2269            .unwrap();
2270
2271        assert_eq!(bob_sender_verified.sender_data.to_type(), SenderDataType::SenderVerified);
2272        assert!(bob_sender_verified.backed_up());
2273        assert!(!bob_sender_verified.has_been_imported());
2274
2275        let alice_unknown_device = database
2276            .get_inbound_group_session(
2277                room_id!("!SRstFdydzrGwJYtVfm:localhost"),
2278                "IrydwXkRk2N2AqUMIVmLL3oJgMq14R9KId0P/uSD100",
2279            )
2280            .await
2281            .unwrap()
2282            .unwrap();
2283
2284        assert_eq!(alice_unknown_device.sender_data.to_type(), SenderDataType::UnknownDevice);
2285        assert!(alice_unknown_device.backed_up());
2286        assert!(alice_unknown_device.has_been_imported());
2287
2288        let carl_tofu_session = database
2289            .get_inbound_group_session(
2290                room_id!("!OgRiTRMaUzLdpCeDBM:localhost"),
2291                "OGB9lObr9kWUvha9tB5sMfOF/Mztk24JwQz/nwg3iFQ",
2292            )
2293            .await
2294            .unwrap()
2295            .unwrap();
2296
2297        assert_eq!(carl_tofu_session.sender_data.to_type(), SenderDataType::SenderUnverified);
2298        assert!(carl_tofu_session.backed_up());
2299        assert!(!carl_tofu_session.has_been_imported());
2300
2301        // Load outbound sessions
2302        database
2303            .get_outbound_group_session(room_id!("!OgRiTRMaUzLdpCeDBM:localhost"))
2304            .await
2305            .unwrap()
2306            .unwrap();
2307        database
2308            .get_outbound_group_session(room_id!("!ZIwZcFqZVAYLAqVjfV:localhost"))
2309            .await
2310            .unwrap()
2311            .unwrap();
2312        database
2313            .get_outbound_group_session(room_id!("!SRstFdydzrGwJYtVfm:localhost"))
2314            .await
2315            .unwrap()
2316            .unwrap();
2317
2318        let withheld_info = database
2319            .get_withheld_info(
2320                room_id!("!OgRiTRMaUzLdpCeDBM:localhost"),
2321                "SASgZ+EklvAF4QxJclMlDRlmL0fAMjAJJIKFMdb4Ht0",
2322            )
2323            .await
2324            .expect("This session should be withheld")
2325            .unwrap();
2326
2327        assert_eq!(withheld_info.content.withheld_code(), WithheldCode::Unverified);
2328
2329        let backup_keys = database.load_backup_keys().await.expect("backup key should be cached");
2330        assert_eq!(backup_keys.backup_version.unwrap(), "6");
2331        assert!(backup_keys.decryption_key.is_some());
2332    }
2333
2334    /// Test that we migrate the secrets inbox properly.
2335    ///
2336    /// The format for the secrets inbox changed in version 17. Previously, the
2337    /// secrets inbox stored a full `GossippedSecrets` struct. In version 17,
2338    /// the secrets inbox now stores only the secret.
2339    #[async_test]
2340    async fn test_secrets_inbox_migration() {
2341        use std::ops::Deref;
2342
2343        use matrix_sdk_crypto::{
2344            GossipRequest, GossippedSecret, SecretInfo,
2345            types::events::{
2346                olm_v1::{DecryptedSecretSendEvent, OlmV1Keys},
2347                secret_send::SecretSendContent,
2348            },
2349            vodozemac::Ed25519SecretKey,
2350        };
2351        use ruma::{TransactionId, events::secret::request::SecretName, owned_user_id};
2352
2353        use crate::utils::{EncryptableStore, SqliteAsyncConnExt};
2354
2355        // Create a database with version 16
2356        let tmpdir = tempdir().unwrap();
2357        let config = SqliteStoreConfig::new(tmpdir.path());
2358        let pool = config.build_pool_of_connections(super::DATABASE_NAME).unwrap();
2359        let conn = pool.get().await.unwrap();
2360        let version = super::initialize_store(&conn, 0).await.unwrap();
2361        let old_data_store = SqliteCryptoStore::create_raw(
2362            config.secret.clone(),
2363            pool,
2364            conn,
2365            config.pool_config(),
2366            config.runtime_config(),
2367        )
2368        .await
2369        .unwrap();
2370        super::run_migrations(&old_data_store, version, Some(16)).await.unwrap();
2371        old_data_store.write().await.unwrap().wal_checkpoint().await;
2372
2373        // Store a secret using the old format
2374        let secret = GossippedSecret {
2375            secret_name: SecretName::CrossSigningMasterKey,
2376            gossip_request: GossipRequest {
2377                request_recipient: owned_user_id!("@alice:example.com"),
2378                request_id: TransactionId::new(),
2379                info: SecretInfo::SecretRequest(SecretName::CrossSigningMasterKey),
2380                sent_out: true,
2381            },
2382            event: DecryptedSecretSendEvent {
2383                sender: owned_user_id!("@alice:example.com"),
2384                recipient: owned_user_id!("@alice:example.com"),
2385                keys: OlmV1Keys { ed25519: Ed25519SecretKey::new().public_key() },
2386                recipient_keys: OlmV1Keys { ed25519: Ed25519SecretKey::new().public_key() },
2387                sender_device_keys: None,
2388                content: SecretSendContent::new(
2389                    "abc".into(),
2390                    "It is a secret to everybody".to_owned(),
2391                ),
2392            },
2393        };
2394        let value = old_data_store.serialize_json(&secret).unwrap();
2395        old_data_store
2396            .write()
2397            .await
2398            .unwrap()
2399            .prepare("INSERT INTO secrets (secret_name, data) VALUES (?1, ?2)", |mut stmt| {
2400                stmt.execute((SecretName::CrossSigningMasterKey.to_string(), value))
2401            })
2402            .await
2403            .unwrap();
2404
2405        // After we open the store, the data will be migrated
2406        let store = SqliteCryptoStore::open_with_config(&config).await.unwrap();
2407
2408        // and we should be able to read the secrets from the inbox
2409        let secrets =
2410            store.get_secrets_from_inbox(&SecretName::CrossSigningMasterKey).await.unwrap();
2411        assert_eq!(secrets.len(), 1);
2412        assert_eq!(secrets[0].deref(), "It is a secret to everybody");
2413    }
2414
2415    /// Test that we migrate the key requests table properly.
2416    ///
2417    /// Version 18 added a new column, with a unique index on the column,
2418    /// meaning that it can now only store one request per requested secret/key.
2419    /// Test that when we migrate from an older version that has multiple
2420    /// requests for the same secret, it only keeps one.
2421    #[async_test]
2422    async fn test_key_requests_migration() {
2423        use matrix_sdk_crypto::{GossipRequest, SecretInfo};
2424        use ruma::{TransactionId, events::secret::request::SecretName, owned_user_id};
2425
2426        use crate::utils::{EncryptableStore, SqliteAsyncConnExt};
2427
2428        // Create a database with version 16
2429        let tmpdir = tempdir().unwrap();
2430        let config = SqliteStoreConfig::new(tmpdir.path());
2431        let pool = config.build_pool_of_connections(super::DATABASE_NAME).unwrap();
2432        let conn = pool.get().await.unwrap();
2433        let version = super::initialize_store(&conn, 0).await.unwrap();
2434        let old_data_store = SqliteCryptoStore::create_raw(
2435            config.secret.clone(),
2436            pool,
2437            conn,
2438            config.pool_config(),
2439            config.runtime_config(),
2440        )
2441        .await
2442        .unwrap();
2443        super::run_migrations(&old_data_store, version, Some(16)).await.unwrap();
2444        old_data_store.write().await.unwrap().wal_checkpoint().await;
2445
2446        // Store a secret using the old format
2447        let recovery_request1 = GossipRequest {
2448            request_recipient: owned_user_id!("@alice:example.com"),
2449            request_id: TransactionId::new(),
2450            info: SecretInfo::SecretRequest(SecretName::RecoveryKey),
2451            sent_out: true,
2452        };
2453        let serialized_recovery_request1 =
2454            old_data_store.serialize_value(&recovery_request1).unwrap();
2455        let recovery_request2 = GossipRequest {
2456            request_recipient: owned_user_id!("@alice:example.com"),
2457            request_id: TransactionId::new(),
2458            info: SecretInfo::SecretRequest(SecretName::RecoveryKey),
2459            sent_out: true,
2460        };
2461        let serialized_recovery_request2 =
2462            old_data_store.serialize_value(&recovery_request2).unwrap();
2463        let msk_request = GossipRequest {
2464            request_recipient: owned_user_id!("@alice:example.com"),
2465            request_id: TransactionId::new(),
2466            info: SecretInfo::SecretRequest(SecretName::CrossSigningMasterKey),
2467            sent_out: true,
2468        };
2469        let serialized_msk_request = old_data_store.serialize_value(&msk_request).unwrap();
2470        let recovery_request1_clone = recovery_request1.clone();
2471        let recovery_request2_clone = recovery_request2.clone();
2472        let msk_request_clone = msk_request.clone();
2473        old_data_store
2474            .write()
2475            .await
2476            .unwrap()
2477            .prepare(
2478                "INSERT INTO key_requests (request_id, sent_out, data) VALUES (?1, ?2, ?3)",
2479                move |mut stmt| {
2480                    stmt.execute((
2481                        old_data_store.encode_key(
2482                            "key_requests",
2483                            recovery_request1_clone.request_id.as_bytes(),
2484                        ),
2485                        recovery_request1_clone.sent_out,
2486                        serialized_recovery_request1,
2487                    ))?;
2488                    stmt.execute((
2489                        old_data_store.encode_key(
2490                            "key_requests",
2491                            recovery_request2_clone.request_id.as_bytes(),
2492                        ),
2493                        recovery_request2_clone.sent_out,
2494                        serialized_recovery_request2,
2495                    ))?;
2496                    stmt.execute((
2497                        old_data_store
2498                            .encode_key("key_requests", msk_request_clone.request_id.as_bytes()),
2499                        msk_request_clone.sent_out,
2500                        serialized_msk_request,
2501                    ))
2502                },
2503            )
2504            .await
2505            .unwrap();
2506
2507        // After we open the store, the data will be migrated
2508        let store = SqliteCryptoStore::open_with_config(&config).await.unwrap();
2509
2510        // and we should be able to read one request for the recovery key and
2511        // one request for the MSK
2512        if let Some(GossipRequest {
2513            request_id,
2514            info: SecretInfo::SecretRequest(SecretName::RecoveryKey),
2515            ..
2516        }) = store
2517            .get_secret_request_by_info(&SecretInfo::SecretRequest(SecretName::RecoveryKey))
2518            .await
2519            .unwrap()
2520        {
2521            if request_id == recovery_request1.request_id {
2522                assert!(
2523                    store
2524                        .get_outgoing_secret_requests(&recovery_request2.request_id)
2525                        .await
2526                        .unwrap()
2527                        .is_none()
2528                );
2529            } else if request_id == recovery_request2.request_id {
2530                assert!(
2531                    store
2532                        .get_outgoing_secret_requests(&recovery_request1.request_id)
2533                        .await
2534                        .unwrap()
2535                        .is_none()
2536                );
2537            } else {
2538                panic!("unexpected record found");
2539            }
2540        } else {
2541            panic!("expected to get a secret request");
2542        }
2543        if let Some(GossipRequest {
2544            request_id,
2545            info: SecretInfo::SecretRequest(SecretName::CrossSigningMasterKey),
2546            ..
2547        }) = store
2548            .get_secret_request_by_info(&SecretInfo::SecretRequest(
2549                SecretName::CrossSigningMasterKey,
2550            ))
2551            .await
2552            .unwrap()
2553        {
2554            assert_eq!(request_id, msk_request.request_id);
2555        } else {
2556            panic!("expected to get a secret request");
2557        }
2558    }
2559
2560    async fn get_store(
2561        name: &str,
2562        passphrase: Option<&str>,
2563        clear_data: bool,
2564    ) -> SqliteCryptoStore {
2565        let tmpdir_path = TMP_DIR.path().join(name);
2566
2567        if clear_data {
2568            let _ = fs::remove_dir_all(&tmpdir_path).await;
2569        }
2570
2571        SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), passphrase)
2572            .await
2573            .expect("Can't create a secret protected store")
2574    }
2575
2576    cryptostore_integration_tests!();
2577    cryptostore_integration_tests_time!();
2578}
2579
2580#[cfg(test)]
2581mod encrypted_tests {
2582    use std::sync::LazyLock;
2583
2584    use matrix_sdk_crypto::{cryptostore_integration_tests, cryptostore_integration_tests_time};
2585    use tempfile::{TempDir, tempdir};
2586    use tokio::fs;
2587
2588    use super::SqliteCryptoStore;
2589
2590    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2591
2592    async fn get_store(
2593        name: &str,
2594        passphrase: Option<&str>,
2595        clear_data: bool,
2596    ) -> SqliteCryptoStore {
2597        let tmpdir_path = TMP_DIR.path().join(name);
2598        let pass = passphrase.unwrap_or("default_test_password");
2599
2600        if clear_data {
2601            let _ = fs::remove_dir_all(&tmpdir_path).await;
2602        }
2603
2604        SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), Some(pass))
2605            .await
2606            .expect("Can't create a secret protected store")
2607    }
2608
2609    cryptostore_integration_tests!();
2610    cryptostore_integration_tests_time!();
2611}
2612
2613#[cfg(test)]
2614mod close_reopen_tests {
2615    use std::sync::LazyLock;
2616
2617    use matrix_sdk_crypto::store::CryptoStore;
2618    use matrix_sdk_test::async_test;
2619    use tempfile::{TempDir, tempdir};
2620
2621    use super::SqliteCryptoStore;
2622
2623    static TMP_DIR: LazyLock<TempDir> = LazyLock::new(|| tempdir().unwrap());
2624
2625    async fn new_store(name: &str) -> SqliteCryptoStore {
2626        let tmpdir_path = TMP_DIR.path().join(name);
2627        SqliteCryptoStore::open(tmpdir_path, None).await.unwrap()
2628    }
2629
2630    #[async_test]
2631    async fn test_close_completes_without_timeout() {
2632        let store = new_store("close_no_timeout").await;
2633
2634        // Close should complete quickly without hitting the 5s timeout.
2635        let start = std::time::Instant::now();
2636        store.close().await.unwrap();
2637        let elapsed = start.elapsed();
2638
2639        assert!(
2640            elapsed < std::time::Duration::from_secs(2),
2641            "close() took {elapsed:?}, expected < 2s (no timeout)"
2642        );
2643
2644        // Connections should be None after close.
2645        let guard = store.connections.lock().await;
2646        assert!(guard.is_none(), "connections should be None after close");
2647    }
2648
2649    #[async_test]
2650    async fn test_reopen_restores_connections() {
2651        let store = new_store("reopen_restores").await;
2652
2653        store.close().await.unwrap();
2654
2655        {
2656            let guard = store.connections.lock().await;
2657            assert!(guard.is_none());
2658        }
2659
2660        store.reopen().await.unwrap();
2661
2662        {
2663            let guard = store.connections.lock().await;
2664            assert!(guard.is_some(), "connections should be Some after reopen");
2665        }
2666    }
2667
2668    #[async_test]
2669    async fn test_close_is_idempotent() {
2670        let store = new_store("close_idempotent").await;
2671
2672        store.close().await.unwrap();
2673        // Second close should be a no-op.
2674        store.close().await.unwrap();
2675
2676        let guard = store.connections.lock().await;
2677        assert!(guard.is_none());
2678    }
2679
2680    #[async_test]
2681    async fn test_reopen_is_idempotent() {
2682        let store = new_store("reopen_idempotent").await;
2683
2684        // Reopen on an active store should be a no-op.
2685        store.reopen().await.unwrap();
2686
2687        let guard = store.connections.lock().await;
2688        assert!(guard.is_some());
2689    }
2690
2691    #[async_test]
2692    async fn test_read_fails_when_closed() {
2693        let store = new_store("read_fails_closed").await;
2694        store.close().await.unwrap();
2695
2696        let err = store.load_account().await;
2697        assert!(err.is_err(), "read should fail when closed");
2698
2699        let err_msg = err.unwrap_err().to_string();
2700        assert!(err_msg.contains("closed"), "error should mention 'closed', got: {err_msg}");
2701    }
2702
2703    #[async_test]
2704    async fn test_operations_work_after_reopen() {
2705        let store = new_store("ops_after_reopen").await;
2706
2707        store.close().await.unwrap();
2708        store.reopen().await.unwrap();
2709
2710        // A read operation should work immediately after reopen.
2711        let account = store.load_account().await;
2712        assert!(account.is_ok(), "load_account should succeed after reopen");
2713        // No account was saved, so this should be None.
2714        assert!(account.unwrap().is_none());
2715    }
2716
2717    #[async_test]
2718    async fn test_multiple_close_reopen_cycles() {
2719        let store = new_store("multi_cycles").await;
2720
2721        for _ in 0..5 {
2722            store.close().await.unwrap();
2723            store.reopen().await.unwrap();
2724
2725            // After each cycle, the store should be fully operational.
2726            let account = store.load_account().await;
2727            assert!(account.is_ok(), "store should work after close/reopen cycle");
2728        }
2729    }
2730
2731    #[async_test]
2732    async fn test_pool_is_fully_drained_after_close() {
2733        let store = new_store("pool_drained").await;
2734
2735        // Do a few reads to exercise the pool.
2736        let _ = store.load_account().await;
2737        let _ = store.load_account().await;
2738
2739        store.close().await.unwrap();
2740
2741        // After close, the connections field should be None.
2742        let guard = store.connections.lock().await;
2743        assert!(guard.is_none(), "all connections should be released after close");
2744    }
2745
2746    #[async_test]
2747    async fn test_close_waits_for_held_read_connection_to_drain() {
2748        let store = new_store("held_read_drain").await;
2749
2750        // Acquire a read connection and hold it, simulating an in-flight read.
2751        let held_conn = store.read().await.unwrap();
2752
2753        // Spawn close in a background task — it will close the pool and then
2754        // poll-wait for pool.status().size == 0 in the drain loop.
2755        let store_clone = store.clone();
2756        let close_handle = tokio::spawn(async move {
2757            store_clone.close().await.unwrap();
2758        });
2759
2760        // Give close() a moment to close the pool and enter the drain loop.
2761        tokio::time::sleep(std::time::Duration::from_millis(200)).await;
2762
2763        // The close task should still be running because we hold a connection.
2764        assert!(!close_handle.is_finished(), "close should be waiting for the held connection");
2765
2766        // Release the held connection — this lets pool.status().size drop to 0.
2767        drop(held_conn);
2768
2769        // Now close should complete promptly (well within the 5s timeout).
2770        let timeout = tokio::time::timeout(std::time::Duration::from_secs(3), close_handle).await;
2771        assert!(timeout.is_ok(), "close should complete after the held connection is released");
2772        timeout.unwrap().unwrap();
2773
2774        // Verify the store is fully closed.
2775        let guard = store.connections.lock().await;
2776        assert!(guard.is_none(), "connections should be None after close");
2777    }
2778}