core_crypto_keystore/connection/
mod.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17pub mod platform {
18    cfg_if::cfg_if! {
19        if #[cfg(target_family = "wasm")] {
20            mod wasm;
21            pub use wasm::keystore_v_1_0_0;
22            pub use self::wasm::WasmConnection as KeystoreDatabaseConnection;
23            pub use wasm::storage;
24            pub use self::wasm::storage::WasmStorageTransaction as TransactionWrapper;
25        } else {
26            mod generic;
27            pub use self::generic::SqlCipherConnection as KeystoreDatabaseConnection;
28            pub use self::generic::TransactionWrapper;
29        }
30    }
31}
32
33pub use self::platform::*;
34use crate::entities::{Entity, EntityFindParams, StringEntityId};
35use std::ops::DerefMut;
36
37use crate::entities::{EntityTransactionExt, UniqueEntity};
38use crate::transaction::KeystoreTransaction;
39use crate::{CryptoKeystoreError, CryptoKeystoreResult};
40use async_lock::{Mutex, MutexGuard, Semaphore};
41use std::sync::Arc;
42
43/// Limit on the length of a blob to be stored in the database.
44///
45/// This limit applies to both SQLCipher-backed stores and WASM.
46/// This limit is conservative on purpose when targeting WASM, as the lower bound that exists is Safari with a limit of 1GB per origin.
47///
48/// See: [SQLite limits](https://www.sqlite.org/limits.html)
49/// See: [IndexedDB limits](https://stackoverflow.com/a/63019999/1934177)
50pub const MAX_BLOB_LEN: usize = 1_000_000_000;
51
52#[cfg(not(target_family = "wasm"))]
53// ? Because of UniFFI async requirements, we need our keystore to be Send as well now
54pub trait DatabaseConnectionRequirements: Sized + Send {}
55#[cfg(target_family = "wasm")]
56// ? On the other hand, things cannot be Send on WASM because of platform restrictions (all things are copied across the FFI)
57pub trait DatabaseConnectionRequirements: Sized {}
58
59#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
60#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
61pub trait DatabaseConnection<'a>: DatabaseConnectionRequirements {
62    type Connection: 'a;
63
64    async fn open(name: &str, key: &str) -> CryptoKeystoreResult<Self>;
65
66    async fn open_in_memory(name: &str, key: &str) -> CryptoKeystoreResult<Self>;
67
68    async fn close(self) -> CryptoKeystoreResult<()>;
69
70    /// Default implementation of wipe
71    async fn wipe(self) -> CryptoKeystoreResult<()> {
72        self.close().await
73    }
74
75    fn check_buffer_size(size: usize) -> CryptoKeystoreResult<()> {
76        #[cfg(not(target_family = "wasm"))]
77        if size > i32::MAX as usize {
78            return Err(CryptoKeystoreError::BlobTooBig);
79        }
80
81        if size >= MAX_BLOB_LEN {
82            return Err(CryptoKeystoreError::BlobTooBig);
83        }
84
85        Ok(())
86    }
87}
88
89#[derive(Debug, Clone)]
90pub struct Connection {
91    pub(crate) conn: Arc<Mutex<KeystoreDatabaseConnection>>,
92    pub(crate) transaction: Arc<Mutex<Option<KeystoreTransaction>>>,
93    transaction_semaphore: Arc<Semaphore>,
94}
95
96const ALLOWED_CONCURRENT_TRANSACTIONS_COUNT: usize = 1;
97
98/// Interface to fetch from the database either from the connection directly or through a
99/// transaaction
100#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
101#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
102pub trait FetchFromDatabase: Send + Sync {
103    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
104        &self,
105        id: &[u8],
106    ) -> CryptoKeystoreResult<Option<E>>;
107
108    async fn find_unique<U: UniqueEntity<ConnectionType = KeystoreDatabaseConnection>>(
109        &self,
110    ) -> CryptoKeystoreResult<U>;
111
112    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
113        &self,
114        params: EntityFindParams,
115    ) -> CryptoKeystoreResult<Vec<E>>;
116
117    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
118        &self,
119        ids: &[Vec<u8>],
120    ) -> CryptoKeystoreResult<Vec<E>>;
121    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize>;
122}
123
124// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
125unsafe impl Send for Connection {}
126// SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
127unsafe impl Sync for Connection {}
128
129impl Connection {
130    pub async fn open_with_key(name: impl AsRef<str>, key: impl AsRef<str>) -> CryptoKeystoreResult<Self> {
131        let conn = KeystoreDatabaseConnection::open(name.as_ref(), key.as_ref())
132            .await?
133            .into();
134        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
135        let conn = Arc::new(conn);
136        Ok(Self {
137            conn,
138            transaction: Default::default(),
139            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
140        })
141    }
142
143    pub async fn open_in_memory_with_key(name: impl AsRef<str>, key: impl AsRef<str>) -> CryptoKeystoreResult<Self> {
144        let conn = KeystoreDatabaseConnection::open_in_memory(name.as_ref(), key.as_ref())
145            .await?
146            .into();
147        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
148        let conn = Arc::new(conn);
149        Ok(Self {
150            conn,
151            transaction: Default::default(),
152            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
153        })
154    }
155
156    pub async fn borrow_conn(&self) -> CryptoKeystoreResult<MutexGuard<'_, KeystoreDatabaseConnection>> {
157        Ok(self.conn.lock().await)
158    }
159
160    pub async fn wipe(self) -> CryptoKeystoreResult<()> {
161        if self.transaction.lock().await.is_some() {
162            return Err(CryptoKeystoreError::TransactionInProgress {
163                attempted_operation: "wipe()".to_string(),
164            });
165        }
166        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
167        conn.wipe().await?;
168        Ok(())
169    }
170
171    pub async fn close(self) -> CryptoKeystoreResult<()> {
172        if self.transaction.lock().await.is_some() {
173            return Err(CryptoKeystoreError::TransactionInProgress {
174                attempted_operation: "close()".to_string(),
175            });
176        }
177        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
178        conn.close().await?;
179        Ok(())
180    }
181
182    /// Waits for the current transaction to be committed or rolled back, then starts a new one.
183    pub async fn new_transaction(&self) -> CryptoKeystoreResult<()> {
184        let semaphore = self.transaction_semaphore.acquire_arc().await;
185        let mut transaction_guard = self.transaction.lock().await;
186        *transaction_guard = Some(KeystoreTransaction::new(semaphore).await?);
187        Ok(())
188    }
189
190    pub async fn commit_transaction(&self) -> CryptoKeystoreResult<()> {
191        let mut transaction_guard = self.transaction.lock().await;
192        let Some(transaction) = transaction_guard.as_ref() else {
193            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
194        };
195        transaction.commit(self).await?;
196        *transaction_guard = None;
197        Ok(())
198    }
199
200    pub async fn rollback_transaction(&self) -> CryptoKeystoreResult<()> {
201        let mut transaction_guard = self.transaction.lock().await;
202        if transaction_guard.is_none() {
203            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
204        };
205        *transaction_guard = None;
206        Ok(())
207    }
208
209    pub async fn child_groups<
210        E: Entity<ConnectionType = KeystoreDatabaseConnection> + crate::entities::PersistedMlsGroupExt + Sync,
211    >(
212        &self,
213        entity: E,
214    ) -> CryptoKeystoreResult<Vec<E>> {
215        let mut conn = self.conn.lock().await;
216        let persisted_records = entity.child_groups(conn.deref_mut()).await?;
217
218        let transaction_guard = self.transaction.lock().await;
219        let Some(transaction) = transaction_guard.as_ref() else {
220            return Ok(persisted_records);
221        };
222        transaction.child_groups(entity, persisted_records).await
223    }
224
225    pub async fn save<E: Entity<ConnectionType = KeystoreDatabaseConnection> + Sync + EntityTransactionExt>(
226        &self,
227        entity: E,
228    ) -> CryptoKeystoreResult<E> {
229        let transaction_guard = self.transaction.lock().await;
230        let Some(transaction) = transaction_guard.as_ref() else {
231            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
232        };
233        transaction.save_mut(entity).await
234    }
235
236    pub async fn remove<
237        E: Entity<ConnectionType = KeystoreDatabaseConnection> + EntityTransactionExt,
238        S: AsRef<[u8]>,
239    >(
240        &self,
241        id: S,
242    ) -> CryptoKeystoreResult<()> {
243        let transaction_guard = self.transaction.lock().await;
244        let Some(transaction) = transaction_guard.as_ref() else {
245            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
246        };
247        transaction.remove::<E, S>(id).await
248    }
249
250    pub async fn cred_delete_by_credential(&self, cred: Vec<u8>) -> CryptoKeystoreResult<()> {
251        let transaction_guard = self.transaction.lock().await;
252        let Some(transaction) = transaction_guard.as_ref() else {
253            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
254        };
255        transaction.cred_delete_by_credential(cred).await
256    }
257}
258
259#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
260#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
261impl FetchFromDatabase for Connection {
262    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
263        &self,
264        id: &[u8],
265    ) -> CryptoKeystoreResult<Option<E>> {
266        // If a transaction is in progress...
267        if let Some(transaction) = self.transaction.lock().await.as_ref() {
268            //... and it has information about this entity, ...
269            if let Some(result) = transaction.find::<E>(id).await? {
270                // ... return that result
271                return Ok(result);
272            }
273        }
274
275        // Otherwise get it from the database
276        let mut conn = self.conn.lock().await;
277        E::find_one(&mut conn, &id.into()).await
278    }
279
280    async fn find_unique<U: UniqueEntity>(&self) -> CryptoKeystoreResult<U> {
281        // If a transaction is in progress...
282        if let Some(transaction) = self.transaction.lock().await.as_ref() {
283            //... and it has information about this entity, ...
284            if let Some(result) = transaction.find_unique::<U>().await? {
285                // ... return that result
286                return Ok(result);
287            }
288        }
289        // Otherwise get it from the database
290        let mut conn = self.conn.lock().await;
291        U::find_unique(&mut conn).await
292    }
293
294    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
295        &self,
296        params: EntityFindParams,
297    ) -> CryptoKeystoreResult<Vec<E>> {
298        let mut conn = self.conn.lock().await;
299        let persisted_records = E::find_all(&mut conn, params.clone()).await?;
300
301        let transaction_guard = self.transaction.lock().await;
302        let Some(transaction) = transaction_guard.as_ref() else {
303            return Ok(persisted_records);
304        };
305        transaction.find_all(persisted_records, params).await
306    }
307
308    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
309        &self,
310        ids: &[Vec<u8>],
311    ) -> CryptoKeystoreResult<Vec<E>> {
312        let entity_ids: Vec<StringEntityId> = ids.iter().map(|id| id.as_slice().into()).collect();
313        let mut conn = self.conn.lock().await;
314        let persisted_records = E::find_many(&mut conn, &entity_ids).await?;
315
316        let transaction_guard = self.transaction.lock().await;
317        let Some(transaction) = transaction_guard.as_ref() else {
318            return Ok(persisted_records);
319        };
320        transaction.find_many(persisted_records, ids).await
321    }
322
323    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize> {
324        if self.transaction.lock().await.is_some() {
325            // Unfortunately, we have to do this because of possible record id overlap
326            // between cache and db.
327            return Ok(self.find_all::<E>(Default::default()).await?.len());
328        };
329        let mut conn = self.conn.lock().await;
330        E::count(&mut conn).await
331    }
332}