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