1#![cfg_attr(
16 not(any(
17 feature = "state-store",
18 feature = "crypto-store",
19 feature = "event-cache-store",
20 feature = "media-store"
21 )),
22 allow(dead_code, unused_imports)
23)]
24
25mod connection;
26#[cfg(feature = "crypto-store")]
27mod crypto_store;
28mod error;
29#[cfg(feature = "event-cache-store")]
30mod event_cache_store;
31#[cfg(feature = "media-store")]
32mod media_store;
33#[cfg(feature = "state-store")]
34mod state_store;
35mod utils;
36use std::{
37 cmp::max,
38 fmt,
39 path::{Path, PathBuf},
40};
41
42use deadpool::managed::PoolConfig;
43use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
44
45#[cfg(feature = "crypto-store")]
46pub use self::crypto_store::SqliteCryptoStore;
47pub use self::error::OpenStoreError;
48#[cfg(feature = "event-cache-store")]
49pub use self::event_cache_store::SqliteEventCacheStore;
50#[cfg(feature = "media-store")]
51pub use self::media_store::SqliteMediaStore;
52#[cfg(feature = "state-store")]
53pub use self::state_store::{DATABASE_NAME as STATE_STORE_DATABASE_NAME, SqliteStateStore};
54
55#[cfg(feature = "uniffi")]
56uniffi::setup_scaffolding!();
57
58#[cfg(test)]
59matrix_sdk_test_utils::init_tracing_for_tests!();
60
61#[derive(Clone, Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
63pub enum Secret {
64 Key(Zeroizing<Vec<u8>>),
66 PassPhrase(Zeroizing<String>),
68 HighEntropyPassPhrase {
71 key: Zeroizing<Vec<u8>>,
72 #[zeroize(skip)]
73 base64_variant: Base64Variant,
74 },
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
84pub enum Base64Variant {
85 Unpadded,
87 Padded,
89}
90
91#[derive(Clone)]
93pub struct SqliteStoreConfig {
94 path: PathBuf,
96 secret: Option<Secret>,
98 pool_config: PoolConfig,
100 runtime_config: RuntimeConfig,
102}
103
104impl fmt::Debug for SqliteStoreConfig {
105 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106 formatter
107 .debug_struct("SqliteStoreConfig")
108 .field("path", &self.path)
109 .field("pool_config", &self.pool_config)
110 .field("runtime_config", &self.runtime_config)
111 .finish_non_exhaustive()
112 }
113}
114
115const POOL_MINIMUM_SIZE: usize = 2;
120
121impl SqliteStoreConfig {
122 pub fn new<P>(path: P) -> Self
125 where
126 P: AsRef<Path>,
127 {
128 Self {
129 path: path.as_ref().to_path_buf(),
130 pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
131 runtime_config: RuntimeConfig::default(),
132 secret: None,
133 }
134 }
135
136 pub fn with_low_memory_config<P>(path: P) -> Self
146 where
147 P: AsRef<Path>,
148 {
149 Self::new(path)
150 .pool_max_size(num_cpus::get_physical())
152 .cache_size(500_000)
154 .journal_size_limit(2_000_000)
156 }
157
158 pub fn path<P>(mut self, path: P) -> Self
160 where
161 P: AsRef<Path>,
162 {
163 self.path = path.as_ref().to_path_buf();
164 self
165 }
166
167 pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
173 self.secret =
174 passphrase.map(|passphrase| Secret::PassPhrase(Zeroizing::new(passphrase.to_owned())));
175 self
176 }
177
178 pub fn high_entropy_passphrase(
193 mut self,
194 passphrase: Option<&[u8]>,
195 base64_variant: Base64Variant,
196 ) -> Self {
197 if let Some(passphrase) = passphrase {
198 let key = Zeroizing::new(passphrase.to_vec());
199 self.secret = Some(Secret::HighEntropyPassPhrase { key, base64_variant });
200 }
201
202 self
203 }
204
205 pub fn key(mut self, key: Option<&[u8]>) -> Self {
209 if let Some(key) = key {
210 let key = Zeroizing::new(key.to_vec());
211 self.secret = Some(Secret::Key(key));
212 }
213
214 self
215 }
216
217 pub fn pool_max_size(mut self, max_size: usize) -> Self {
221 self.pool_config.max_size = max(POOL_MINIMUM_SIZE, max_size);
222 self
223 }
224
225 pub fn optimize(mut self, optimize: bool) -> Self {
237 self.runtime_config.optimize = optimize;
238 self
239 }
240
241 pub fn cache_size(mut self, cache_size: u32) -> Self {
249 self.runtime_config.cache_size = cache_size;
250 self
251 }
252
253 pub fn journal_size_limit(mut self, limit: u32) -> Self {
273 self.runtime_config.journal_size_limit = limit;
274 self
275 }
276
277 pub(crate) fn pool_config(&self) -> PoolConfig {
279 self.pool_config
280 }
281
282 pub(crate) fn runtime_config(&self) -> RuntimeConfig {
284 self.runtime_config
285 }
286
287 pub fn build_pool_of_connections(
289 &self,
290 database_name: &str,
291 ) -> Result<connection::Pool, connection::CreatePoolError> {
292 let path = self.path.join(database_name);
293 let manager = connection::Manager::new(path);
294
295 connection::Pool::builder(manager)
296 .config(self.pool_config)
297 .runtime(connection::RUNTIME)
298 .build()
299 .map_err(connection::CreatePoolError::Build)
300 }
301}
302
303#[derive(Clone, Copy, Debug)]
308struct RuntimeConfig {
309 optimize: bool,
311
312 cache_size: u32,
315
316 journal_size_limit: u32,
320}
321
322impl Default for RuntimeConfig {
323 fn default() -> Self {
324 Self {
325 optimize: true,
327 cache_size: 2_000_000,
329 journal_size_limit: 10_000_000,
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use std::{
338 ops::Not,
339 path::{Path, PathBuf},
340 };
341
342 use zeroize::Zeroizing;
343
344 use super::{POOL_MINIMUM_SIZE, Secret, SqliteStoreConfig};
345
346 #[test]
347 fn test_new() {
348 let store_config = SqliteStoreConfig::new(Path::new("foo"));
349
350 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical() * 4);
351 assert!(store_config.runtime_config.optimize);
352 assert_eq!(store_config.runtime_config.cache_size, 2_000_000);
353 assert_eq!(store_config.runtime_config.journal_size_limit, 10_000_000);
354 }
355
356 #[test]
357 fn test_with_low_memory_config() {
358 let store_config = SqliteStoreConfig::with_low_memory_config(Path::new("foo"));
359
360 assert_eq!(store_config.pool_config.max_size, num_cpus::get_physical());
361 assert!(store_config.runtime_config.optimize);
362 assert_eq!(store_config.runtime_config.cache_size, 500_000);
363 assert_eq!(store_config.runtime_config.journal_size_limit, 2_000_000);
364 }
365
366 #[test]
367 fn test_store_config_when_passphrase() {
368 let store_config = SqliteStoreConfig::new(Path::new("foo"))
369 .passphrase(Some("bar"))
370 .pool_max_size(42)
371 .optimize(false)
372 .cache_size(43)
373 .journal_size_limit(44);
374
375 assert_eq!(store_config.path, PathBuf::from("foo"));
376 assert_eq!(store_config.secret, Some(Secret::PassPhrase("bar".to_owned().into())));
377 assert_eq!(store_config.pool_config.max_size, 42);
378 assert!(store_config.runtime_config.optimize.not());
379 assert_eq!(store_config.runtime_config.cache_size, 43);
380 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
381 }
382
383 #[test]
384 fn test_store_config_when_key() {
385 let store_config = SqliteStoreConfig::new(Path::new("foo"))
386 .key(Some(&[
387 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
388 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
389 ]))
390 .pool_max_size(42)
391 .optimize(false)
392 .cache_size(43)
393 .journal_size_limit(44);
394
395 assert_eq!(store_config.path, PathBuf::from("foo"));
396 assert_eq!(
397 store_config.secret,
398 Some(Secret::Key(Zeroizing::new(vec![
399 143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238, 61,
400 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
401 ])))
402 );
403 assert_eq!(store_config.pool_config.max_size, 42);
404 assert!(store_config.runtime_config.optimize.not());
405 assert_eq!(store_config.runtime_config.cache_size, 43);
406 assert_eq!(store_config.runtime_config.journal_size_limit, 44);
407 }
408
409 #[test]
410 fn test_store_config_path() {
411 let store_config = SqliteStoreConfig::new(Path::new("foo")).path(Path::new("bar"));
412
413 assert_eq!(store_config.path, PathBuf::from("bar"));
414 }
415
416 #[test]
417 fn test_pool_size_has_a_minimum() {
418 let store_config = SqliteStoreConfig::new(Path::new("foo")).pool_max_size(1);
419
420 assert_eq!(store_config.pool_config.max_size, POOL_MINIMUM_SIZE);
421 }
422}