#[cfg(feature = "software_platform")]
pub mod key_custody;
#[cfg(feature = "software_platform")]
pub use key_custody::SqliteKeyCustody;
use std::path::Path;
use std::sync::Mutex;
use rusqlite::Connection;
use zeroize::Zeroize;
use crate::error::PlatformError;
use crate::traits::Storage;
pub struct SqliteStorage {
conn: Mutex<Connection>,
}
impl SqliteStorage {
pub fn new(dir: &Path, key: &[u8]) -> Result<Self, PlatformError> {
std::fs::create_dir_all(dir)
.map_err(|e| PlatformError::StorageError(format!("failed to create directory: {e}")))?;
let db_path = dir.join("scp.db");
let conn = Connection::open(&db_path)
.map_err(|e| PlatformError::StorageError(format!("failed to open database: {e}")))?;
let mut hex_key = hex::encode(key);
let mut pragma_sql = format!(
"PRAGMA key = \"x'{hex_key}'\";\n\
PRAGMA cipher_page_size = 4096;\n\
PRAGMA kdf_iter = 256000;\n\
PRAGMA cipher_hmac_algorithm = HMAC_SHA512;\n\
PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512;"
);
hex_key.zeroize();
let result = conn.execute_batch(&pragma_sql);
pragma_sql.zeroize();
result.map_err(|e| {
PlatformError::StorageError(format!("failed to set SQLCipher pragmas: {e}"))
})?;
conn.pragma_update(None, "journal_mode", "WAL")
.map_err(|e| PlatformError::StorageError(format!("failed to enable WAL mode: {e}")))?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS kv (\
key TEXT PRIMARY KEY, \
value BLOB NOT NULL\
) WITHOUT ROWID;",
)
.map_err(|e| PlatformError::StorageError(format!("failed to create schema: {e}")))?;
Ok(Self {
conn: Mutex::new(conn),
})
}
}
fn prefix_successor(prefix: &str) -> Option<String> {
let mut bytes = prefix.as_bytes().to_vec();
while let Some(last) = bytes.last_mut() {
if *last < 0xFF {
*last += 1;
return String::from_utf8(bytes).ok();
}
bytes.pop();
}
None
}
fn lock_conn(
conn: &Mutex<Connection>,
) -> Result<std::sync::MutexGuard<'_, Connection>, PlatformError> {
conn.lock()
.map_err(|e| PlatformError::StorageError(format!("mutex poisoned: {e}")))
}
fn collect_keys(
stmt: &mut rusqlite::CachedStatement<'_>,
params: &[&dyn rusqlite::types::ToSql],
) -> Result<Vec<String>, PlatformError> {
stmt.query_map(params, |row| row.get::<_, String>(0))
.map_err(|e| PlatformError::StorageError(format!("list_keys failed: {e}")))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| PlatformError::StorageError(format!("list_keys row failed: {e}")))
}
#[allow(clippy::manual_async_fn)]
impl Storage for SqliteStorage {
fn store(
&self,
key: &str,
data: &[u8],
) -> impl Future<Output = Result<(), PlatformError>> + Send {
let key = key.to_owned();
let data = data.to_vec();
async move {
let conn = lock_conn(&self.conn)?;
conn.execute(
"INSERT OR REPLACE INTO kv (key, value) VALUES (?1, ?2)",
rusqlite::params![key, data],
)
.map_err(|e| PlatformError::StorageError(format!("store failed: {e}")))?;
drop(conn);
Ok(())
}
}
fn retrieve(
&self,
key: &str,
) -> impl Future<Output = Result<Option<Vec<u8>>, PlatformError>> + Send {
let key = key.to_owned();
async move {
let conn = lock_conn(&self.conn)?;
let mut stmt = conn
.prepare_cached("SELECT value FROM kv WHERE key = ?1")
.map_err(|e| PlatformError::StorageError(format!("prepare failed: {e}")))?;
let result = stmt
.query_row(rusqlite::params![key], |row| row.get::<_, Vec<u8>>(0))
.optional()
.map_err(|e| PlatformError::StorageError(format!("retrieve failed: {e}")))?;
drop(stmt);
drop(conn);
Ok(result)
}
}
fn delete(&self, key: &str) -> impl Future<Output = Result<(), PlatformError>> + Send {
let key = key.to_owned();
async move {
let conn = lock_conn(&self.conn)?;
conn.execute("DELETE FROM kv WHERE key = ?1", rusqlite::params![key])
.map_err(|e| PlatformError::StorageError(format!("delete failed: {e}")))?;
drop(conn);
Ok(())
}
}
fn list_keys(
&self,
prefix: &str,
) -> impl Future<Output = Result<Vec<String>, PlatformError>> + Send {
let prefix = prefix.to_owned();
async move {
let conn = lock_conn(&self.conn)?;
let keys = if prefix.is_empty() {
let mut stmt = conn
.prepare_cached("SELECT key FROM kv ORDER BY key")
.map_err(|e| PlatformError::StorageError(format!("prepare failed: {e}")))?;
collect_keys(&mut stmt, &[])
} else {
prefix_successor(&prefix).map_or_else(
|| {
let mut stmt = conn
.prepare_cached("SELECT key FROM kv WHERE key >= ?1 ORDER BY key")
.map_err(|e| {
PlatformError::StorageError(format!("prepare failed: {e}"))
})?;
collect_keys(&mut stmt, &[&prefix as &dyn rusqlite::types::ToSql])
},
|successor| {
let mut stmt = conn
.prepare_cached(
"SELECT key FROM kv \
WHERE key >= ?1 AND key < ?2 ORDER BY key",
)
.map_err(|e| {
PlatformError::StorageError(format!("prepare failed: {e}"))
})?;
collect_keys(
&mut stmt,
&[
&prefix as &dyn rusqlite::types::ToSql,
&successor as &dyn rusqlite::types::ToSql,
],
)
},
)
}?;
drop(conn);
Ok(keys)
}
}
fn delete_prefix(
&self,
prefix: &str,
) -> impl Future<Output = Result<u64, PlatformError>> + Send {
let prefix = prefix.to_owned();
async move {
let conn = lock_conn(&self.conn)?;
let deleted = prefix_successor(&prefix)
.map_or_else(
|| conn.execute("DELETE FROM kv WHERE key >= ?1", rusqlite::params![prefix]),
|successor| {
conn.execute(
"DELETE FROM kv WHERE key >= ?1 AND key < ?2",
rusqlite::params![prefix, successor],
)
},
)
.map_err(|e| PlatformError::StorageError(format!("delete_prefix failed: {e}")))?;
drop(conn);
Ok(deleted as u64)
}
}
fn exists(&self, key: &str) -> impl Future<Output = Result<bool, PlatformError>> + Send {
let key = key.to_owned();
async move {
let conn = lock_conn(&self.conn)?;
let mut stmt = conn
.prepare_cached("SELECT COUNT(*) FROM kv WHERE key = ?1")
.map_err(|e| PlatformError::StorageError(format!("prepare failed: {e}")))?;
let count: i64 = stmt
.query_row(rusqlite::params![key], |row| row.get(0))
.map_err(|e| PlatformError::StorageError(format!("exists failed: {e}")))?;
drop(stmt);
drop(conn);
Ok(count > 0)
}
}
}
trait OptionalResult<T> {
fn optional(self) -> Result<Option<T>, rusqlite::Error>;
}
impl<T> OptionalResult<T> for Result<T, rusqlite::Error> {
fn optional(self) -> Result<Option<T>, rusqlite::Error> {
match self {
Ok(v) => Ok(Some(v)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefix_successor_normal() {
assert_eq!(prefix_successor("ctx/"), Some("ctx0".to_owned()));
}
#[test]
fn prefix_successor_empty() {
assert_eq!(prefix_successor(""), None);
}
#[test]
fn prefix_successor_single_char() {
assert_eq!(prefix_successor("a"), Some("b".to_owned()));
}
}