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: DatabaseConnectionRequirements {
62    async fn open(name: &str, key: &str) -> CryptoKeystoreResult<Self>;
63
64    async fn open_in_memory(name: &str, key: &str) -> CryptoKeystoreResult<Self>;
65
66    async fn close(self) -> CryptoKeystoreResult<()>;
67
68    /// Default implementation of wipe
69    async fn wipe(self) -> CryptoKeystoreResult<()> {
70        self.close().await
71    }
72
73    fn check_buffer_size(size: usize) -> CryptoKeystoreResult<()> {
74        #[cfg(not(target_family = "wasm"))]
75        if size > i32::MAX as usize {
76            return Err(CryptoKeystoreError::BlobTooBig);
77        }
78
79        if size >= MAX_BLOB_LEN {
80            return Err(CryptoKeystoreError::BlobTooBig);
81        }
82
83        Ok(())
84    }
85    #[cfg(not(target_family = "wasm"))]
86    async fn new_transaction(&mut self) -> CryptoKeystoreResult<TransactionWrapper<'_>>;
87    #[cfg(target_family = "wasm")]
88    async fn new_transaction<T: AsRef<str>>(
89        &mut self,
90        tables: &[T],
91    ) -> CryptoKeystoreResult<crate::connection::TransactionWrapper<'_>>;
92}
93
94#[derive(Debug, Clone)]
95pub struct Connection {
96    pub(crate) conn: Arc<Mutex<KeystoreDatabaseConnection>>,
97    pub(crate) transaction: Arc<Mutex<Option<KeystoreTransaction>>>,
98    transaction_semaphore: Arc<Semaphore>,
99}
100
101const ALLOWED_CONCURRENT_TRANSACTIONS_COUNT: usize = 1;
102
103/// Interface to fetch from the database either from the connection directly or through a
104/// transaaction
105#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
106#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
107pub trait FetchFromDatabase: Send + Sync {
108    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
109        &self,
110        id: &[u8],
111    ) -> CryptoKeystoreResult<Option<E>>;
112
113    async fn find_unique<U: UniqueEntity<ConnectionType = KeystoreDatabaseConnection>>(
114        &self,
115    ) -> CryptoKeystoreResult<U>;
116
117    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
118        &self,
119        params: EntityFindParams,
120    ) -> CryptoKeystoreResult<Vec<E>>;
121
122    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
123        &self,
124        ids: &[Vec<u8>],
125    ) -> CryptoKeystoreResult<Vec<E>>;
126    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize>;
127}
128
129// * SAFETY: this has mutexes and atomics protecting underlying data so this is safe to share between threads
130unsafe impl Send for Connection {}
131unsafe impl Sync for Connection {}
132
133impl Connection {
134    pub async fn open_with_key(name: impl AsRef<str>, key: impl AsRef<str>) -> CryptoKeystoreResult<Self> {
135        let conn = KeystoreDatabaseConnection::open(name.as_ref(), key.as_ref())
136            .await?
137            .into();
138        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
139        let conn = Arc::new(conn);
140        Ok(Self {
141            conn,
142            transaction: Default::default(),
143            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
144        })
145    }
146
147    pub async fn open_in_memory_with_key(name: impl AsRef<str>, key: impl AsRef<str>) -> CryptoKeystoreResult<Self> {
148        let conn = KeystoreDatabaseConnection::open_in_memory(name.as_ref(), key.as_ref())
149            .await?
150            .into();
151        #[allow(clippy::arc_with_non_send_sync)] // see https://github.com/rustwasm/wasm-bindgen/pull/955
152        let conn = Arc::new(conn);
153        Ok(Self {
154            conn,
155            transaction: Default::default(),
156            transaction_semaphore: Arc::new(Semaphore::new(ALLOWED_CONCURRENT_TRANSACTIONS_COUNT)),
157        })
158    }
159
160    pub async fn borrow_conn(&self) -> CryptoKeystoreResult<MutexGuard<'_, KeystoreDatabaseConnection>> {
161        Ok(self.conn.lock().await)
162    }
163
164    pub async fn wipe(self) -> CryptoKeystoreResult<()> {
165        if self.transaction.lock().await.is_some() {
166            return Err(CryptoKeystoreError::TransactionInProgress {
167                attempted_operation: "wipe()".to_string(),
168            });
169        }
170        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
171        conn.wipe().await?;
172        Ok(())
173    }
174
175    pub async fn close(self) -> CryptoKeystoreResult<()> {
176        if self.transaction.lock().await.is_some() {
177            return Err(CryptoKeystoreError::TransactionInProgress {
178                attempted_operation: "close()".to_string(),
179            });
180        }
181        let conn: KeystoreDatabaseConnection = Arc::into_inner(self.conn).unwrap().into_inner();
182        conn.close().await?;
183        Ok(())
184    }
185
186    /// Waits for the current transaction to be committed or rolled back, then starts a new one.
187    pub async fn new_transaction(&self) -> CryptoKeystoreResult<()> {
188        let semaphore = self.transaction_semaphore.acquire_arc().await;
189        let mut transaction_guard = self.transaction.lock().await;
190        *transaction_guard = Some(KeystoreTransaction::new(semaphore).await?);
191        Ok(())
192    }
193
194    pub async fn commit_transaction(&self) -> CryptoKeystoreResult<()> {
195        let mut transaction_guard = self.transaction.lock().await;
196        let Some(transaction) = transaction_guard.as_ref() else {
197            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
198        };
199        transaction.commit(self).await?;
200        *transaction_guard = None;
201        Ok(())
202    }
203
204    pub async fn rollback_transaction(&self) -> CryptoKeystoreResult<()> {
205        let mut transaction_guard = self.transaction.lock().await;
206        if transaction_guard.is_none() {
207            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
208        };
209        *transaction_guard = None;
210        Ok(())
211    }
212
213    pub async fn child_groups<
214        E: Entity<ConnectionType = KeystoreDatabaseConnection> + crate::entities::PersistedMlsGroupExt + Sync,
215    >(
216        &self,
217        entity: E,
218    ) -> CryptoKeystoreResult<Vec<E>> {
219        let mut conn = self.conn.lock().await;
220        let persisted_records = entity.child_groups(conn.deref_mut()).await?;
221
222        let transaction_guard = self.transaction.lock().await;
223        let Some(transaction) = transaction_guard.as_ref() else {
224            return Ok(persisted_records);
225        };
226        transaction.child_groups(entity, persisted_records).await
227    }
228
229    pub async fn save<E: Entity<ConnectionType = KeystoreDatabaseConnection> + Sync + EntityTransactionExt>(
230        &self,
231        entity: E,
232    ) -> CryptoKeystoreResult<E> {
233        let transaction_guard = self.transaction.lock().await;
234        let Some(transaction) = transaction_guard.as_ref() else {
235            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
236        };
237        transaction.save_mut(entity).await
238    }
239
240    pub async fn remove<
241        E: Entity<ConnectionType = KeystoreDatabaseConnection> + EntityTransactionExt,
242        S: AsRef<[u8]>,
243    >(
244        &self,
245        id: S,
246    ) -> CryptoKeystoreResult<()> {
247        let transaction_guard = self.transaction.lock().await;
248        let Some(transaction) = transaction_guard.as_ref() else {
249            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
250        };
251        transaction.remove::<E, S>(id).await
252    }
253
254    pub async fn cred_delete_by_credential(&self, cred: Vec<u8>) -> CryptoKeystoreResult<()> {
255        let transaction_guard = self.transaction.lock().await;
256        let Some(transaction) = transaction_guard.as_ref() else {
257            return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
258        };
259        transaction.cred_delete_by_credential(cred).await
260    }
261}
262
263#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
264#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
265impl FetchFromDatabase for Connection {
266    async fn find<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
267        &self,
268        id: &[u8],
269    ) -> CryptoKeystoreResult<Option<E>> {
270        // If a transaction is in progress...
271        if let Some(transaction) = self.transaction.lock().await.as_ref() {
272            //... and it has information about this entity, ...
273            if let Some(result) = transaction.find::<E>(id).await? {
274                // ... return that result
275                return Ok(result);
276            }
277        }
278
279        // Otherwise get it from the database
280        let mut conn = self.conn.lock().await;
281        E::find_one(&mut conn, &id.into()).await
282    }
283
284    async fn find_unique<U: UniqueEntity>(&self) -> CryptoKeystoreResult<U> {
285        // If a transaction is in progress...
286        if let Some(transaction) = self.transaction.lock().await.as_ref() {
287            //... and it has information about this entity, ...
288            if let Some(result) = transaction.find_unique::<U>().await? {
289                // ... return that result
290                return Ok(result);
291            }
292        }
293        // Otherwise get it from the database
294        let mut conn = self.conn.lock().await;
295        U::find_unique(&mut conn).await
296    }
297
298    async fn find_all<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
299        &self,
300        params: EntityFindParams,
301    ) -> CryptoKeystoreResult<Vec<E>> {
302        let mut conn = self.conn.lock().await;
303        let persisted_records = E::find_all(&mut conn, params.clone()).await?;
304
305        let transaction_guard = self.transaction.lock().await;
306        let Some(transaction) = transaction_guard.as_ref() else {
307            return Ok(persisted_records);
308        };
309        transaction.find_all(persisted_records, params).await
310    }
311
312    async fn find_many<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(
313        &self,
314        ids: &[Vec<u8>],
315    ) -> CryptoKeystoreResult<Vec<E>> {
316        let entity_ids: Vec<StringEntityId> = ids.iter().map(|id| id.as_slice().into()).collect();
317        let mut conn = self.conn.lock().await;
318        let persisted_records = E::find_many(&mut conn, &entity_ids).await?;
319
320        let transaction_guard = self.transaction.lock().await;
321        let Some(transaction) = transaction_guard.as_ref() else {
322            return Ok(persisted_records);
323        };
324        transaction.find_many(persisted_records, ids).await
325    }
326
327    async fn count<E: Entity<ConnectionType = KeystoreDatabaseConnection>>(&self) -> CryptoKeystoreResult<usize> {
328        if self.transaction.lock().await.is_some() {
329            // Unfortunately, we have to do this because of possible record id overlap
330            // between cache and db.
331            return Ok(self.find_all::<E>(Default::default()).await?.len());
332        };
333        let mut conn = self.conn.lock().await;
334        E::count(&mut conn).await
335    }
336}