removing dev branch, many changes
This commit is contained in:
17
veilid-core/src/table_store/mod.rs
Normal file
17
veilid-core/src/table_store/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use super::*;
|
||||
|
||||
mod table_db;
|
||||
mod table_store;
|
||||
pub use table_db::*;
|
||||
pub use table_store::*;
|
||||
|
||||
pub mod tests;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod native;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use native::*;
|
53
veilid-core/src/table_store/native.rs
Normal file
53
veilid-core/src/table_store/native.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use super::*;
|
||||
pub use keyvaluedb_sqlite::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TableStoreDriver {
|
||||
config: VeilidConfig,
|
||||
}
|
||||
|
||||
impl TableStoreDriver {
|
||||
pub fn new(config: VeilidConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
fn get_dbpath(&self, table: &str) -> VeilidAPIResult<PathBuf> {
|
||||
let c = self.config.get();
|
||||
let tablestoredir = c.table_store.directory.clone();
|
||||
std::fs::create_dir_all(&tablestoredir).map_err(VeilidAPIError::from)?;
|
||||
|
||||
let dbpath: PathBuf = [tablestoredir, String::from(table)].iter().collect();
|
||||
Ok(dbpath)
|
||||
}
|
||||
|
||||
pub async fn open(&self, table_name: &str, column_count: u32) -> VeilidAPIResult<Database> {
|
||||
let dbpath = self.get_dbpath(&table_name)?;
|
||||
|
||||
// Ensure permissions are correct
|
||||
ensure_file_private_owner(&dbpath).map_err(VeilidAPIError::internal)?;
|
||||
|
||||
let cfg = DatabaseConfig::with_columns(column_count);
|
||||
let db = Database::open(&dbpath, cfg).map_err(VeilidAPIError::from)?;
|
||||
|
||||
// Ensure permissions are correct
|
||||
ensure_file_private_owner(&dbpath).map_err(VeilidAPIError::internal)?;
|
||||
|
||||
trace!(
|
||||
"opened table store '{}' at path '{:?}' with {} columns",
|
||||
table_name,
|
||||
dbpath,
|
||||
column_count
|
||||
);
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, table_name: &str) -> VeilidAPIResult<bool> {
|
||||
let dbpath = self.get_dbpath(&table_name)?;
|
||||
if !dbpath.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
std::fs::remove_file(dbpath).map_err(VeilidAPIError::from)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
382
veilid-core/src/table_store/table_db.rs
Normal file
382
veilid-core/src/table_store/table_db.rs
Normal file
@@ -0,0 +1,382 @@
|
||||
use crate::*;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
use keyvaluedb_web::*;
|
||||
use keyvaluedb::*;
|
||||
} else {
|
||||
use keyvaluedb_sqlite::*;
|
||||
use keyvaluedb::*;
|
||||
}
|
||||
}
|
||||
|
||||
struct CryptInfo {
|
||||
vcrypto: CryptoSystemVersion,
|
||||
key: SharedSecret,
|
||||
}
|
||||
impl CryptInfo {
|
||||
pub fn new(crypto: Crypto, typed_key: TypedSharedSecret) -> Self {
|
||||
let vcrypto = crypto.get(typed_key.kind).unwrap();
|
||||
let key = typed_key.value;
|
||||
Self { vcrypto, key }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableDBUnlockedInner {
|
||||
table: String,
|
||||
table_store: TableStore,
|
||||
database: Database,
|
||||
// Encryption and decryption key will be the same unless configured for an in-place migration
|
||||
encrypt_info: Option<CryptInfo>,
|
||||
decrypt_info: Option<CryptInfo>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableDBUnlockedInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "TableDBInner(table={})", self.table)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TableDBUnlockedInner {
|
||||
fn drop(&mut self) {
|
||||
self.table_store.on_table_db_drop(self.table.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableDB {
|
||||
unlocked_inner: Arc<TableDBUnlockedInner>,
|
||||
}
|
||||
|
||||
impl TableDB {
|
||||
pub(super) fn new(
|
||||
table: String,
|
||||
table_store: TableStore,
|
||||
crypto: Crypto,
|
||||
database: Database,
|
||||
encryption_key: Option<TypedSharedSecret>,
|
||||
decryption_key: Option<TypedSharedSecret>,
|
||||
) -> Self {
|
||||
let encrypt_info = encryption_key.map(|ek| CryptInfo::new(crypto.clone(), ek));
|
||||
let decrypt_info = decryption_key.map(|dk| CryptInfo::new(crypto.clone(), dk));
|
||||
|
||||
Self {
|
||||
unlocked_inner: Arc::new(TableDBUnlockedInner {
|
||||
table,
|
||||
table_store,
|
||||
database,
|
||||
encrypt_info,
|
||||
decrypt_info,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn try_new_from_weak_inner(weak_inner: Weak<TableDBUnlockedInner>) -> Option<Self> {
|
||||
weak_inner.upgrade().map(|table_db_unlocked_inner| Self {
|
||||
unlocked_inner: table_db_unlocked_inner,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn weak_inner(&self) -> Weak<TableDBUnlockedInner> {
|
||||
Arc::downgrade(&self.unlocked_inner)
|
||||
}
|
||||
|
||||
/// Get the total number of columns in the TableDB
|
||||
pub fn get_column_count(&self) -> VeilidAPIResult<u32> {
|
||||
let db = &self.unlocked_inner.database;
|
||||
db.num_columns().map_err(VeilidAPIError::from)
|
||||
}
|
||||
|
||||
/// Encrypt buffer using encrypt key and prepend nonce to output
|
||||
/// Keyed nonces are unique because keys must be unique
|
||||
/// Normally they must be sequential or random, but the critical
|
||||
/// requirement is that they are different for each encryption
|
||||
/// but if the contents are guaranteed to be unique, then a nonce
|
||||
/// can be generated from the hash of the contents and the encryption key itself
|
||||
fn maybe_encrypt(&self, data: &[u8], keyed_nonce: bool) -> Vec<u8> {
|
||||
if let Some(ei) = &self.unlocked_inner.encrypt_info {
|
||||
let mut out = unsafe { unaligned_u8_vec_uninit(NONCE_LENGTH + data.len()) };
|
||||
|
||||
if keyed_nonce {
|
||||
// Key content nonce
|
||||
let mut noncedata = Vec::with_capacity(data.len() + PUBLIC_KEY_LENGTH);
|
||||
noncedata.extend_from_slice(data);
|
||||
noncedata.extend_from_slice(&ei.key.bytes);
|
||||
let noncehash = ei.vcrypto.generate_hash(&noncedata);
|
||||
out[0..NONCE_LENGTH].copy_from_slice(&noncehash[0..NONCE_LENGTH])
|
||||
} else {
|
||||
// Random nonce
|
||||
random_bytes(&mut out[0..NONCE_LENGTH]);
|
||||
}
|
||||
|
||||
let (nonce, encout) = out.split_at_mut(NONCE_LENGTH);
|
||||
ei.vcrypto.crypt_b2b_no_auth(
|
||||
data,
|
||||
encout,
|
||||
(nonce as &[u8]).try_into().unwrap(),
|
||||
&ei.key,
|
||||
);
|
||||
out
|
||||
} else {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypt buffer using decrypt key with nonce prepended to input
|
||||
fn maybe_decrypt(&self, data: &[u8]) -> Vec<u8> {
|
||||
if let Some(di) = &self.unlocked_inner.decrypt_info {
|
||||
assert!(data.len() >= NONCE_LENGTH);
|
||||
if data.len() == NONCE_LENGTH {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut out = unsafe { unaligned_u8_vec_uninit(data.len() - NONCE_LENGTH) };
|
||||
|
||||
di.vcrypto.crypt_b2b_no_auth(
|
||||
&data[NONCE_LENGTH..],
|
||||
&mut out,
|
||||
(&data[0..NONCE_LENGTH]).try_into().unwrap(),
|
||||
&di.key,
|
||||
);
|
||||
out
|
||||
} else {
|
||||
data.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the list of keys in a column of the TableDB
|
||||
pub async fn get_keys(&self, col: u32) -> VeilidAPIResult<Vec<Vec<u8>>> {
|
||||
let db = self.unlocked_inner.database.clone();
|
||||
let mut out = Vec::new();
|
||||
db.iter_keys(col, None, |k| {
|
||||
out.push(self.maybe_decrypt(k));
|
||||
Ok(Option::<()>::None)
|
||||
})
|
||||
.await
|
||||
.map_err(VeilidAPIError::from)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Start a TableDB write transaction. The transaction object must be committed or rolled back before dropping.
|
||||
pub fn transact(&self) -> TableDBTransaction {
|
||||
let dbt = self.unlocked_inner.database.transaction();
|
||||
TableDBTransaction::new(self.clone(), dbt)
|
||||
}
|
||||
|
||||
/// Store a key with a value in a column in the TableDB. Performs a single transaction immediately.
|
||||
pub async fn store(&self, col: u32, key: &[u8], value: &[u8]) -> VeilidAPIResult<()> {
|
||||
let db = self.unlocked_inner.database.clone();
|
||||
let mut dbt = db.transaction();
|
||||
dbt.put(
|
||||
col,
|
||||
self.maybe_encrypt(key, true),
|
||||
self.maybe_encrypt(value, false),
|
||||
);
|
||||
db.write(dbt).await.map_err(VeilidAPIError::generic)
|
||||
}
|
||||
|
||||
/// Store a key in rkyv format with a value in a column in the TableDB. Performs a single transaction immediately.
|
||||
pub async fn store_rkyv<T>(&self, col: u32, key: &[u8], value: &T) -> VeilidAPIResult<()>
|
||||
where
|
||||
T: RkyvSerialize<DefaultVeilidRkyvSerializer>,
|
||||
{
|
||||
let value = to_rkyv(value)?;
|
||||
self.store(col, key, &value).await
|
||||
}
|
||||
|
||||
/// Store a key in json format with a value in a column in the TableDB. Performs a single transaction immediately.
|
||||
pub async fn store_json<T>(&self, col: u32, key: &[u8], value: &T) -> VeilidAPIResult<()>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let value = serde_json::to_vec(value).map_err(VeilidAPIError::internal)?;
|
||||
self.store(col, key, &value).await
|
||||
}
|
||||
|
||||
/// Read a key from a column in the TableDB immediately.
|
||||
pub async fn load(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<Vec<u8>>> {
|
||||
let db = self.unlocked_inner.database.clone();
|
||||
let key = self.maybe_encrypt(key, true);
|
||||
Ok(db
|
||||
.get(col, &key)
|
||||
.await
|
||||
.map_err(VeilidAPIError::from)?
|
||||
.map(|v| self.maybe_decrypt(&v)))
|
||||
}
|
||||
|
||||
/// Read an rkyv key from a column in the TableDB immediately
|
||||
pub async fn load_rkyv<T>(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<T>>
|
||||
where
|
||||
T: RkyvArchive,
|
||||
<T as RkyvArchive>::Archived:
|
||||
for<'t> CheckBytes<rkyv::validation::validators::DefaultValidator<'t>>,
|
||||
<T as RkyvArchive>::Archived: RkyvDeserialize<T, VeilidSharedDeserializeMap>,
|
||||
{
|
||||
let out = match self.load(col, key).await? {
|
||||
Some(v) => Some(from_rkyv(v)?),
|
||||
None => None,
|
||||
};
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Read an serde-json key from a column in the TableDB immediately
|
||||
pub async fn load_json<T>(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<T>>
|
||||
where
|
||||
T: for<'de> serde::Deserialize<'de>,
|
||||
{
|
||||
let out = match self.load(col, key).await? {
|
||||
Some(v) => Some(serde_json::from_slice(&v).map_err(VeilidAPIError::internal)?),
|
||||
None => None,
|
||||
};
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Delete key with from a column in the TableDB
|
||||
pub async fn delete(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<Vec<u8>>> {
|
||||
let key = self.maybe_encrypt(key, true);
|
||||
|
||||
let db = self.unlocked_inner.database.clone();
|
||||
let old_value = db
|
||||
.delete(col, &key)
|
||||
.await
|
||||
.map_err(VeilidAPIError::from)?
|
||||
.map(|v| self.maybe_decrypt(&v));
|
||||
Ok(old_value)
|
||||
}
|
||||
|
||||
/// Delete rkyv key with from a column in the TableDB
|
||||
pub async fn delete_rkyv<T>(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<T>>
|
||||
where
|
||||
T: RkyvArchive,
|
||||
<T as RkyvArchive>::Archived:
|
||||
for<'t> CheckBytes<rkyv::validation::validators::DefaultValidator<'t>>,
|
||||
<T as RkyvArchive>::Archived: RkyvDeserialize<T, VeilidSharedDeserializeMap>,
|
||||
{
|
||||
let old_value = match self.delete(col, key).await? {
|
||||
Some(v) => Some(from_rkyv(v)?),
|
||||
None => None,
|
||||
};
|
||||
Ok(old_value)
|
||||
}
|
||||
|
||||
/// Delete serde-json key with from a column in the TableDB
|
||||
pub async fn delete_json<T>(&self, col: u32, key: &[u8]) -> VeilidAPIResult<Option<T>>
|
||||
where
|
||||
T: for<'de> serde::Deserialize<'de>,
|
||||
{
|
||||
let old_value = match self.delete(col, key).await? {
|
||||
Some(v) => Some(serde_json::from_slice(&v).map_err(VeilidAPIError::internal)?),
|
||||
None => None,
|
||||
};
|
||||
Ok(old_value)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct TableDBTransactionInner {
|
||||
dbt: Option<DBTransaction>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TableDBTransactionInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"TableDBTransactionInner({})",
|
||||
match &self.dbt {
|
||||
Some(dbt) => format!("len={}", dbt.ops.len()),
|
||||
None => "".to_owned(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A TableDB transaction
|
||||
/// Atomically commits a group of writes or deletes to the TableDB
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TableDBTransaction {
|
||||
db: TableDB,
|
||||
inner: Arc<Mutex<TableDBTransactionInner>>,
|
||||
}
|
||||
|
||||
impl TableDBTransaction {
|
||||
fn new(db: TableDB, dbt: DBTransaction) -> Self {
|
||||
Self {
|
||||
db,
|
||||
inner: Arc::new(Mutex::new(TableDBTransactionInner { dbt: Some(dbt) })),
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit the transaction. Performs all actions atomically.
|
||||
pub async fn commit(self) -> VeilidAPIResult<()> {
|
||||
let dbt = {
|
||||
let mut inner = self.inner.lock();
|
||||
inner
|
||||
.dbt
|
||||
.take()
|
||||
.ok_or_else(|| VeilidAPIError::generic("transaction already completed"))?
|
||||
};
|
||||
|
||||
let db = self.db.unlocked_inner.database.clone();
|
||||
db.write(dbt)
|
||||
.await
|
||||
.map_err(|e| VeilidAPIError::generic(format!("commit failed, transaction lost: {}", e)))
|
||||
}
|
||||
|
||||
/// Rollback the transaction. Does nothing to the TableDB.
|
||||
pub fn rollback(self) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.dbt = None;
|
||||
}
|
||||
|
||||
/// Store a key with a value in a column in the TableDB
|
||||
pub fn store(&self, col: u32, key: &[u8], value: &[u8]) {
|
||||
let key = self.db.maybe_encrypt(key, true);
|
||||
let value = self.db.maybe_encrypt(value, false);
|
||||
let mut inner = self.inner.lock();
|
||||
inner.dbt.as_mut().unwrap().put_owned(col, key, value);
|
||||
}
|
||||
|
||||
/// Store a key in rkyv format with a value in a column in the TableDB
|
||||
pub fn store_rkyv<T>(&self, col: u32, key: &[u8], value: &T) -> VeilidAPIResult<()>
|
||||
where
|
||||
T: RkyvSerialize<DefaultVeilidRkyvSerializer>,
|
||||
{
|
||||
let value = to_rkyv(value)?;
|
||||
let key = self.db.maybe_encrypt(key, true);
|
||||
let value = self.db.maybe_encrypt(&value, false);
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
inner.dbt.as_mut().unwrap().put_owned(col, key, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store a key in rkyv format with a value in a column in the TableDB
|
||||
pub fn store_json<T>(&self, col: u32, key: &[u8], value: &T) -> VeilidAPIResult<()>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let value = serde_json::to_vec(value).map_err(VeilidAPIError::internal)?;
|
||||
let key = self.db.maybe_encrypt(key, true);
|
||||
let value = self.db.maybe_encrypt(&value, false);
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
inner.dbt.as_mut().unwrap().put_owned(col, key, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete key with from a column in the TableDB
|
||||
pub fn delete(&self, col: u32, key: &[u8]) {
|
||||
let key = self.db.maybe_encrypt(key, true);
|
||||
let mut inner = self.inner.lock();
|
||||
inner.dbt.as_mut().unwrap().delete_owned(col, key);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TableDBTransactionInner {
|
||||
fn drop(&mut self) {
|
||||
if self.dbt.is_some() {
|
||||
warn!("Dropped transaction without commit or rollback");
|
||||
}
|
||||
}
|
||||
}
|
557
veilid-core/src/table_store/table_store.rs
Normal file
557
veilid-core/src/table_store/table_store.rs
Normal file
@@ -0,0 +1,557 @@
|
||||
use super::*;
|
||||
use keyvaluedb::*;
|
||||
|
||||
struct TableStoreInner {
|
||||
opened: BTreeMap<String, Weak<TableDBUnlockedInner>>,
|
||||
encryption_key: Option<TypedSharedSecret>,
|
||||
all_table_names: HashMap<String, String>,
|
||||
all_tables_db: Option<Database>,
|
||||
crypto: Option<Crypto>,
|
||||
}
|
||||
|
||||
/// Veilid Table Storage
|
||||
/// Database for storing key value pairs persistently and securely across runs
|
||||
#[derive(Clone)]
|
||||
pub struct TableStore {
|
||||
config: VeilidConfig,
|
||||
protected_store: ProtectedStore,
|
||||
table_store_driver: TableStoreDriver,
|
||||
inner: Arc<Mutex<TableStoreInner>>, // Sync mutex here because TableDB drops can happen at any time
|
||||
async_lock: Arc<AsyncMutex<()>>, // Async mutex for operations
|
||||
}
|
||||
|
||||
impl TableStore {
|
||||
fn new_inner() -> TableStoreInner {
|
||||
TableStoreInner {
|
||||
opened: BTreeMap::new(),
|
||||
encryption_key: None,
|
||||
all_table_names: HashMap::new(),
|
||||
all_tables_db: None,
|
||||
crypto: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn new(config: VeilidConfig, protected_store: ProtectedStore) -> Self {
|
||||
let inner = Self::new_inner();
|
||||
let table_store_driver = TableStoreDriver::new(config.clone());
|
||||
|
||||
Self {
|
||||
config,
|
||||
protected_store,
|
||||
inner: Arc::new(Mutex::new(inner)),
|
||||
table_store_driver,
|
||||
async_lock: Arc::new(AsyncMutex::new(())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_crypto(&self, crypto: Crypto) {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.crypto = Some(crypto);
|
||||
}
|
||||
|
||||
// Flush internal control state (must not use crypto)
|
||||
async fn flush(&self) {
|
||||
let (all_table_names_value, all_tables_db) = {
|
||||
let inner = self.inner.lock();
|
||||
let all_table_names_value =
|
||||
to_rkyv(&inner.all_table_names).expect("failed to archive all_table_names");
|
||||
(all_table_names_value, inner.all_tables_db.clone().unwrap())
|
||||
};
|
||||
let mut dbt = DBTransaction::new();
|
||||
dbt.put(0, b"all_table_names", &all_table_names_value);
|
||||
if let Err(e) = all_tables_db.write(dbt).await {
|
||||
error!("failed to write all tables db: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal naming support
|
||||
// Adds rename capability and ensures names of tables are totally unique and valid
|
||||
|
||||
fn namespaced_name(&self, table: &str) -> VeilidAPIResult<String> {
|
||||
if !table
|
||||
.chars()
|
||||
.all(|c| char::is_alphanumeric(c) || c == '_' || c == '-')
|
||||
{
|
||||
apibail_invalid_argument!("table name is invalid", "table", table);
|
||||
}
|
||||
let c = self.config.get();
|
||||
let namespace = c.namespace.clone();
|
||||
Ok(if namespace.is_empty() {
|
||||
table.to_string()
|
||||
} else {
|
||||
format!("_ns_{}_{}", namespace, table)
|
||||
})
|
||||
}
|
||||
|
||||
async fn name_get_or_create(&self, table: &str) -> VeilidAPIResult<String> {
|
||||
let name = self.namespaced_name(table)?;
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
// Do we have this name yet?
|
||||
if let Some(real_name) = inner.all_table_names.get(&name) {
|
||||
return Ok(real_name.clone());
|
||||
}
|
||||
|
||||
// If not, make a new low level name mapping
|
||||
let mut real_name_bytes = [0u8; 32];
|
||||
random_bytes(&mut real_name_bytes);
|
||||
let real_name = data_encoding::BASE64URL_NOPAD.encode(&real_name_bytes);
|
||||
|
||||
if inner
|
||||
.all_table_names
|
||||
.insert(name.to_owned(), real_name.clone())
|
||||
.is_some()
|
||||
{
|
||||
panic!("should not have had some value");
|
||||
};
|
||||
|
||||
Ok(real_name)
|
||||
}
|
||||
|
||||
async fn name_delete(&self, table: &str) -> VeilidAPIResult<Option<String>> {
|
||||
let name = self.namespaced_name(table)?;
|
||||
let mut inner = self.inner.lock();
|
||||
let real_name = inner.all_table_names.remove(&name);
|
||||
Ok(real_name)
|
||||
}
|
||||
|
||||
async fn name_get(&self, table: &str) -> VeilidAPIResult<Option<String>> {
|
||||
let name = self.namespaced_name(table)?;
|
||||
let inner = self.inner.lock();
|
||||
let real_name = inner.all_table_names.get(&name).cloned();
|
||||
Ok(real_name)
|
||||
}
|
||||
|
||||
async fn name_rename(&self, old_table: &str, new_table: &str) -> VeilidAPIResult<()> {
|
||||
let old_name = self.namespaced_name(old_table)?;
|
||||
let new_name = self.namespaced_name(new_table)?;
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
// Ensure new name doesn't exist
|
||||
if inner.all_table_names.contains_key(&new_name) {
|
||||
return Err(VeilidAPIError::generic("new table already exists"));
|
||||
}
|
||||
// Do we have this name yet?
|
||||
let Some(real_name) = inner.all_table_names.remove(&old_name) else {
|
||||
return Err(VeilidAPIError::generic("table does not exist"));
|
||||
};
|
||||
// Insert with new name
|
||||
inner.all_table_names.insert(new_name.to_owned(), real_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete all known tables
|
||||
async fn delete_all(&self) {
|
||||
// Get all tables
|
||||
let real_names = {
|
||||
let mut inner = self.inner.lock();
|
||||
let real_names = inner
|
||||
.all_table_names
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<String>>();
|
||||
inner.all_table_names.clear();
|
||||
real_names
|
||||
};
|
||||
|
||||
// Delete all tables
|
||||
for table_name in real_names {
|
||||
if let Err(e) = self.table_store_driver.delete(&table_name).await {
|
||||
error!("error deleting table: {}", e);
|
||||
}
|
||||
}
|
||||
self.flush().await;
|
||||
}
|
||||
|
||||
pub fn maybe_unprotect_device_encryption_key(
|
||||
&self,
|
||||
dek_bytes: &[u8],
|
||||
device_encryption_key_password: &str,
|
||||
) -> EyreResult<TypedSharedSecret> {
|
||||
// Ensure the key is at least as long as necessary if unencrypted
|
||||
if dek_bytes.len() < (4 + SHARED_SECRET_LENGTH) {
|
||||
bail!("device encryption key is not valid");
|
||||
}
|
||||
|
||||
// Get cryptosystem
|
||||
let kind = FourCC::try_from(&dek_bytes[0..4]).unwrap();
|
||||
let crypto = self.inner.lock().crypto.as_ref().unwrap().clone();
|
||||
let Some(vcrypto) = crypto.get(kind) else {
|
||||
bail!("unsupported cryptosystem");
|
||||
};
|
||||
|
||||
if !device_encryption_key_password.is_empty() {
|
||||
if dek_bytes.len()
|
||||
!= (4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH)
|
||||
{
|
||||
bail!("password protected device encryption key is not valid");
|
||||
}
|
||||
let protected_key = &dek_bytes[4..(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())];
|
||||
let nonce = &dek_bytes[(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead())..];
|
||||
|
||||
let shared_secret = vcrypto
|
||||
.derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce)
|
||||
.wrap_err("failed to derive shared secret")?;
|
||||
let unprotected_key = vcrypto
|
||||
.decrypt_aead(
|
||||
&protected_key,
|
||||
&Nonce::try_from(nonce).wrap_err("invalid nonce")?,
|
||||
&shared_secret,
|
||||
None,
|
||||
)
|
||||
.wrap_err("failed to decrypt device encryption key")?;
|
||||
return Ok(TypedSharedSecret::new(
|
||||
kind,
|
||||
SharedSecret::try_from(unprotected_key.as_slice())
|
||||
.wrap_err("invalid shared secret")?,
|
||||
));
|
||||
}
|
||||
|
||||
if dek_bytes.len() != (4 + SHARED_SECRET_LENGTH) {
|
||||
bail!("password protected device encryption key is not valid");
|
||||
}
|
||||
|
||||
Ok(TypedSharedSecret::new(
|
||||
kind,
|
||||
SharedSecret::try_from(&dek_bytes[4..])?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn maybe_protect_device_encryption_key(
|
||||
&self,
|
||||
dek: TypedSharedSecret,
|
||||
device_encryption_key_password: &str,
|
||||
) -> EyreResult<Vec<u8>> {
|
||||
// Check if we are to protect the key
|
||||
if device_encryption_key_password.is_empty() {
|
||||
debug!("no dek password");
|
||||
// Return the unprotected key bytes
|
||||
let mut out = Vec::with_capacity(4 + SHARED_SECRET_LENGTH);
|
||||
out.extend_from_slice(&dek.kind.0);
|
||||
out.extend_from_slice(&dek.value.bytes);
|
||||
return Ok(out);
|
||||
}
|
||||
|
||||
// Get cryptosystem
|
||||
let crypto = self.inner.lock().crypto.as_ref().unwrap().clone();
|
||||
let Some(vcrypto) = crypto.get(dek.kind) else {
|
||||
bail!("unsupported cryptosystem");
|
||||
};
|
||||
|
||||
let nonce = vcrypto.random_nonce();
|
||||
let shared_secret = vcrypto
|
||||
.derive_shared_secret(device_encryption_key_password.as_bytes(), &nonce.bytes)
|
||||
.wrap_err("failed to derive shared secret")?;
|
||||
let mut protected_key = vcrypto
|
||||
.encrypt_aead(
|
||||
&dek.value.bytes,
|
||||
&Nonce::try_from(nonce).wrap_err("invalid nonce")?,
|
||||
&shared_secret,
|
||||
None,
|
||||
)
|
||||
.wrap_err("failed to decrypt device encryption key")?;
|
||||
let mut out =
|
||||
Vec::with_capacity(4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH);
|
||||
out.extend_from_slice(&dek.kind.0);
|
||||
out.append(&mut protected_key);
|
||||
out.extend_from_slice(&nonce.bytes);
|
||||
assert!(out.len() == 4 + SHARED_SECRET_LENGTH + vcrypto.aead_overhead() + NONCE_LENGTH);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
async fn load_device_encryption_key(&self) -> EyreResult<Option<TypedSharedSecret>> {
|
||||
let dek_bytes: Option<Vec<u8>> = self
|
||||
.protected_store
|
||||
.load_user_secret("device_encryption_key")
|
||||
.await?;
|
||||
let Some(dek_bytes) = dek_bytes else {
|
||||
debug!("no device encryption key");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Get device encryption key protection password if we have it
|
||||
let device_encryption_key_password = {
|
||||
let c = self.config.get();
|
||||
c.protected_store.device_encryption_key_password.clone()
|
||||
};
|
||||
|
||||
Ok(Some(self.maybe_unprotect_device_encryption_key(
|
||||
&dek_bytes,
|
||||
&device_encryption_key_password,
|
||||
)?))
|
||||
}
|
||||
async fn save_device_encryption_key(
|
||||
&self,
|
||||
device_encryption_key: Option<TypedSharedSecret>,
|
||||
) -> EyreResult<()> {
|
||||
let Some(device_encryption_key) = device_encryption_key else {
|
||||
// Remove the device encryption key
|
||||
let existed = self
|
||||
.protected_store
|
||||
.remove_user_secret("device_encryption_key")
|
||||
.await?;
|
||||
debug!("removed device encryption key. existed: {}", existed);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Get new device encryption key protection password if we are changing it
|
||||
let new_device_encryption_key_password = {
|
||||
let c = self.config.get();
|
||||
c.protected_store.new_device_encryption_key_password.clone()
|
||||
};
|
||||
let device_encryption_key_password =
|
||||
if let Some(new_device_encryption_key_password) = new_device_encryption_key_password {
|
||||
// Change password
|
||||
debug!("changing dek password");
|
||||
self.config
|
||||
.with_mut(|c| {
|
||||
c.protected_store.device_encryption_key_password =
|
||||
new_device_encryption_key_password.clone();
|
||||
Ok(new_device_encryption_key_password)
|
||||
})
|
||||
.unwrap()
|
||||
} else {
|
||||
// Get device encryption key protection password if we have it
|
||||
debug!("saving with existing dek password");
|
||||
let c = self.config.get();
|
||||
c.protected_store.device_encryption_key_password.clone()
|
||||
};
|
||||
|
||||
let dek_bytes = self.maybe_protect_device_encryption_key(
|
||||
device_encryption_key,
|
||||
&device_encryption_key_password,
|
||||
)?;
|
||||
|
||||
// Save the new device encryption key
|
||||
let existed = self
|
||||
.protected_store
|
||||
.save_user_secret("device_encryption_key", &dek_bytes)
|
||||
.await?;
|
||||
debug!("saving device encryption key. existed: {}", existed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn init(&self) -> EyreResult<()> {
|
||||
let _async_guard = self.async_lock.lock().await;
|
||||
|
||||
// Get device encryption key from protected store
|
||||
let mut device_encryption_key = self.load_device_encryption_key().await?;
|
||||
let mut device_encryption_key_changed = false;
|
||||
if let Some(device_encryption_key) = device_encryption_key {
|
||||
// If encryption in current use is not the best encryption, then run table migration
|
||||
let best_kind = best_crypto_kind();
|
||||
if device_encryption_key.kind != best_kind {
|
||||
// XXX: Run migration. See issue #209
|
||||
}
|
||||
} else {
|
||||
// If we don't have an encryption key yet, then make one with the best cryptography and save it
|
||||
let best_kind = best_crypto_kind();
|
||||
let mut shared_secret = SharedSecret::default();
|
||||
random_bytes(&mut shared_secret.bytes);
|
||||
|
||||
device_encryption_key = Some(TypedSharedSecret::new(best_kind, shared_secret));
|
||||
device_encryption_key_changed = true;
|
||||
}
|
||||
|
||||
// Check for password change
|
||||
let changing_password = self
|
||||
.config
|
||||
.get()
|
||||
.protected_store
|
||||
.new_device_encryption_key_password
|
||||
.is_some();
|
||||
|
||||
// Save encryption key if it has changed or if the protecting password wants to change
|
||||
if device_encryption_key_changed || changing_password {
|
||||
self.save_device_encryption_key(device_encryption_key)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Deserialize all table names
|
||||
let all_tables_db = self
|
||||
.table_store_driver
|
||||
.open("__veilid_all_tables", 1)
|
||||
.await
|
||||
.wrap_err("failed to create all tables table")?;
|
||||
match all_tables_db.get(0, b"all_table_names").await {
|
||||
Ok(Some(v)) => match from_rkyv::<HashMap<String, String>>(v) {
|
||||
Ok(all_table_names) => {
|
||||
let mut inner = self.inner.lock();
|
||||
inner.all_table_names = all_table_names;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("could not deserialize __veilid_all_tables: {}", e);
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
// No table names yet, that's okay
|
||||
trace!("__veilid_all_tables is empty");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("could not get __veilid_all_tables: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
inner.encryption_key = device_encryption_key;
|
||||
inner.all_tables_db = Some(all_tables_db);
|
||||
}
|
||||
|
||||
let do_delete = {
|
||||
let c = self.config.get();
|
||||
c.table_store.delete
|
||||
};
|
||||
|
||||
if do_delete {
|
||||
self.delete_all().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn terminate(&self) {
|
||||
let _async_guard = self.async_lock.lock().await;
|
||||
|
||||
self.flush().await;
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
if !inner.opened.is_empty() {
|
||||
panic!(
|
||||
"all open databases should have been closed: {:?}",
|
||||
inner.opened
|
||||
);
|
||||
}
|
||||
inner.all_tables_db = None;
|
||||
inner.all_table_names.clear();
|
||||
inner.encryption_key = None;
|
||||
}
|
||||
|
||||
pub(crate) fn on_table_db_drop(&self, table: String) {
|
||||
let mut inner = self.inner.lock();
|
||||
if inner.opened.remove(&table).is_none() {
|
||||
unreachable!("should have removed an item");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get or create a TableDB database table. If the column count is greater than an
|
||||
/// existing TableDB's column count, the database will be upgraded to add the missing columns
|
||||
pub async fn open(&self, name: &str, column_count: u32) -> VeilidAPIResult<TableDB> {
|
||||
let _async_guard = self.async_lock.lock().await;
|
||||
|
||||
// If we aren't initialized yet, bail
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
if inner.all_tables_db.is_none() {
|
||||
apibail_not_initialized!();
|
||||
}
|
||||
}
|
||||
|
||||
let table_name = self.name_get_or_create(name).await?;
|
||||
|
||||
// See if this table is already opened
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
if let Some(table_db_weak_inner) = inner.opened.get(&table_name) {
|
||||
match TableDB::try_new_from_weak_inner(table_db_weak_inner.clone()) {
|
||||
Some(tdb) => {
|
||||
return Ok(tdb);
|
||||
}
|
||||
None => {
|
||||
inner.opened.remove(&table_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Open table db using platform-specific driver
|
||||
let db = match self
|
||||
.table_store_driver
|
||||
.open(&table_name, column_count)
|
||||
.await
|
||||
{
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
self.name_delete(name).await.expect("cleanup failed");
|
||||
self.flush().await;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Flush table names to disk
|
||||
self.flush().await;
|
||||
|
||||
// Wrap low-level Database in TableDB object
|
||||
let mut inner = self.inner.lock();
|
||||
let table_db = TableDB::new(
|
||||
table_name.clone(),
|
||||
self.clone(),
|
||||
inner.crypto.as_ref().unwrap().clone(),
|
||||
db,
|
||||
inner.encryption_key.clone(),
|
||||
inner.encryption_key.clone(),
|
||||
);
|
||||
|
||||
// Keep track of opened DBs
|
||||
inner
|
||||
.opened
|
||||
.insert(table_name.clone(), table_db.weak_inner());
|
||||
|
||||
Ok(table_db)
|
||||
}
|
||||
|
||||
/// Delete a TableDB table by name
|
||||
pub async fn delete(&self, name: &str) -> VeilidAPIResult<bool> {
|
||||
let _async_guard = self.async_lock.lock().await;
|
||||
// If we aren't initialized yet, bail
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
if inner.all_tables_db.is_none() {
|
||||
apibail_not_initialized!();
|
||||
}
|
||||
}
|
||||
|
||||
let Some(table_name) = self.name_get(name).await? else {
|
||||
// Did not exist in name table
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// See if this table is opened
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
if inner.opened.contains_key(&table_name) {
|
||||
apibail_generic!("Not deleting table that is still opened");
|
||||
}
|
||||
}
|
||||
|
||||
// Delete table db using platform-specific driver
|
||||
let deleted = self.table_store_driver.delete(&table_name).await?;
|
||||
if !deleted {
|
||||
// Table missing? Just remove name
|
||||
self.name_delete(&name)
|
||||
.await
|
||||
.expect("failed to delete name");
|
||||
warn!(
|
||||
"table existed in name table but not in storage: {} : {}",
|
||||
name, table_name
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Rename a TableDB table
|
||||
pub async fn rename(&self, old_name: &str, new_name: &str) -> VeilidAPIResult<()> {
|
||||
let _async_guard = self.async_lock.lock().await;
|
||||
// If we aren't initialized yet, bail
|
||||
{
|
||||
let inner = self.inner.lock();
|
||||
if inner.all_tables_db.is_none() {
|
||||
apibail_not_initialized!();
|
||||
}
|
||||
}
|
||||
trace!("TableStore::rename {} -> {}", old_name, new_name);
|
||||
self.name_rename(old_name, new_name).await
|
||||
}
|
||||
}
|
1
veilid-core/src/table_store/tests/mod.rs
Normal file
1
veilid-core/src/table_store/tests/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod test_table_store;
|
277
veilid-core/src/table_store/tests/test_table_store.rs
Normal file
277
veilid-core/src/table_store/tests/test_table_store.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use crate::tests::test_veilid_config::*;
|
||||
use crate::*;
|
||||
|
||||
async fn startup() -> VeilidAPI {
|
||||
trace!("test_table_store: starting");
|
||||
let (update_callback, config_callback) = setup_veilid_core();
|
||||
api_startup(update_callback, config_callback)
|
||||
.await
|
||||
.expect("startup failed")
|
||||
}
|
||||
|
||||
async fn shutdown(api: VeilidAPI) {
|
||||
trace!("test_table_store: shutting down");
|
||||
api.shutdown().await;
|
||||
trace!("test_table_store: finished");
|
||||
}
|
||||
|
||||
pub async fn test_delete_open_delete(ts: TableStore) {
|
||||
trace!("test_delete_open_delete");
|
||||
|
||||
let _ = ts.delete("test");
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
assert!(
|
||||
ts.delete("test").await.is_err(),
|
||||
"should fail because file is opened"
|
||||
);
|
||||
drop(db);
|
||||
assert!(
|
||||
ts.delete("test").await.is_ok(),
|
||||
"should succeed because file is closed"
|
||||
);
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
assert!(
|
||||
ts.delete("test").await.is_err(),
|
||||
"should fail because file is opened"
|
||||
);
|
||||
drop(db);
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
assert!(
|
||||
ts.delete("test").await.is_err(),
|
||||
"should fail because file is opened"
|
||||
);
|
||||
drop(db);
|
||||
assert!(
|
||||
ts.delete("test").await.is_ok(),
|
||||
"should succeed because file is closed"
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn test_store_delete_load(ts: TableStore) {
|
||||
trace!("test_store_delete_load");
|
||||
|
||||
let _ = ts.delete("test");
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
assert!(
|
||||
ts.delete("test").await.is_err(),
|
||||
"should fail because file is opened"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
db.load(0, b"foo").await.unwrap(),
|
||||
None,
|
||||
"should not load missing key"
|
||||
);
|
||||
assert!(
|
||||
db.store(1, b"foo", b"1234567890").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
assert_eq!(
|
||||
db.load(0, b"foo").await.unwrap(),
|
||||
None,
|
||||
"should not load missing key"
|
||||
);
|
||||
assert_eq!(
|
||||
db.load(1, b"foo").await.unwrap(),
|
||||
Some(b"1234567890".to_vec())
|
||||
);
|
||||
|
||||
assert!(
|
||||
db.store(1, b"bar", b"FNORD").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
assert!(
|
||||
db.store(0, b"bar", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
.await
|
||||
.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
assert!(
|
||||
db.store(2, b"bar", b"FNORD").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
assert!(
|
||||
db.store(2, b"baz", b"QWERTY").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
assert!(
|
||||
db.store(2, b"bar", b"QWERTYUIOP").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
|
||||
assert_eq!(db.load(1, b"bar").await.unwrap(), Some(b"FNORD".to_vec()));
|
||||
assert_eq!(
|
||||
db.load(0, b"bar").await.unwrap(),
|
||||
Some(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_vec())
|
||||
);
|
||||
assert_eq!(
|
||||
db.load(2, b"bar").await.unwrap(),
|
||||
Some(b"QWERTYUIOP".to_vec())
|
||||
);
|
||||
assert_eq!(db.load(2, b"baz").await.unwrap(), Some(b"QWERTY".to_vec()));
|
||||
|
||||
assert_eq!(db.delete(1, b"bar").await.unwrap(), Some(b"FNORD".to_vec()));
|
||||
assert_eq!(db.delete(1, b"bar").await.unwrap(), None);
|
||||
assert!(
|
||||
db.delete(4, b"bar").await.is_err(),
|
||||
"can't delete from column that doesn't exist"
|
||||
);
|
||||
|
||||
drop(db);
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
|
||||
assert_eq!(db.load(1, b"bar").await.unwrap(), None);
|
||||
assert_eq!(
|
||||
db.load(0, b"bar").await.unwrap(),
|
||||
Some(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_vec())
|
||||
);
|
||||
assert_eq!(
|
||||
db.load(2, b"bar").await.unwrap(),
|
||||
Some(b"QWERTYUIOP".to_vec())
|
||||
);
|
||||
assert_eq!(db.load(2, b"baz").await.unwrap(), Some(b"QWERTY".to_vec()));
|
||||
}
|
||||
|
||||
pub async fn test_rkyv(vcrypto: CryptoSystemVersion, ts: TableStore) {
|
||||
trace!("test_rkyv");
|
||||
|
||||
let _ = ts.delete("test");
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
let keypair = vcrypto.generate_keypair();
|
||||
|
||||
assert!(db.store_rkyv(0, b"asdf", &keypair).await.is_ok());
|
||||
|
||||
assert_eq!(db.load_rkyv::<KeyPair>(0, b"qwer").await.unwrap(), None);
|
||||
|
||||
let d = match db.load_rkyv::<KeyPair>(0, b"asdf").await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
panic!("couldn't decode: {}", e);
|
||||
}
|
||||
};
|
||||
assert_eq!(d, Some(keypair), "keys should be equal");
|
||||
|
||||
let d = match db.delete_rkyv::<KeyPair>(0, b"asdf").await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
panic!("couldn't decode: {}", e);
|
||||
}
|
||||
};
|
||||
assert_eq!(d, Some(keypair), "keys should be equal");
|
||||
|
||||
assert!(
|
||||
db.store(1, b"foo", b"1234567890").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
|
||||
assert!(
|
||||
db.load_rkyv::<TypedKey>(1, b"foo").await.is_err(),
|
||||
"should fail to unfreeze"
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn test_json(vcrypto: CryptoSystemVersion, ts: TableStore) {
|
||||
trace!("test_json");
|
||||
|
||||
let _ = ts.delete("test");
|
||||
let db = ts.open("test", 3).await.expect("should have opened");
|
||||
let keypair = vcrypto.generate_keypair();
|
||||
|
||||
assert!(db.store_json(0, b"asdf", &keypair).await.is_ok());
|
||||
|
||||
assert_eq!(db.load_json::<KeyPair>(0, b"qwer").await.unwrap(), None);
|
||||
|
||||
let d = match db.load_json::<KeyPair>(0, b"asdf").await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
panic!("couldn't decode: {}", e);
|
||||
}
|
||||
};
|
||||
assert_eq!(d, Some(keypair), "keys should be equal");
|
||||
|
||||
let d = match db.delete_json::<KeyPair>(0, b"asdf").await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
panic!("couldn't decode: {}", e);
|
||||
}
|
||||
};
|
||||
assert_eq!(d, Some(keypair), "keys should be equal");
|
||||
|
||||
assert!(
|
||||
db.store(1, b"foo", b"1234567890").await.is_ok(),
|
||||
"should store new key"
|
||||
);
|
||||
|
||||
assert!(
|
||||
db.load_json::<TypedKey>(1, b"foo").await.is_err(),
|
||||
"should fail to unfreeze"
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn test_protect_unprotect(vcrypto: CryptoSystemVersion, ts: TableStore) {
|
||||
trace!("test_protect_unprotect");
|
||||
|
||||
let dek1 = TypedSharedSecret::new(
|
||||
vcrypto.kind(),
|
||||
SharedSecret::new([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
]),
|
||||
);
|
||||
let dek2 = TypedSharedSecret::new(
|
||||
vcrypto.kind(),
|
||||
SharedSecret::new([
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0xFF,
|
||||
]),
|
||||
);
|
||||
let dek3 = TypedSharedSecret::new(
|
||||
vcrypto.kind(),
|
||||
SharedSecret::new([0x80u8; SHARED_SECRET_LENGTH]),
|
||||
);
|
||||
|
||||
let deks = [dek1, dek2, dek3];
|
||||
let passwords = ["", " ", " ", "12345678", "|/\\!@#$%^&*()_+", "Ⓜ️", "🔥🔥♾️"];
|
||||
|
||||
for dek in deks {
|
||||
for password in passwords {
|
||||
let dek_bytes = ts
|
||||
.maybe_protect_device_encryption_key(dek, password)
|
||||
.expect(&format!("protect: dek: '{}' pw: '{}'", dek, password));
|
||||
let unprotected = ts
|
||||
.maybe_unprotect_device_encryption_key(&dek_bytes, password)
|
||||
.expect(&format!("unprotect: dek: '{}' pw: '{}'", dek, password));
|
||||
assert_eq!(unprotected, dek);
|
||||
let invalid_password = format!("{}x", password);
|
||||
let _ = ts
|
||||
.maybe_unprotect_device_encryption_key(&dek_bytes, &invalid_password)
|
||||
.expect_err(&format!(
|
||||
"invalid_password: dek: '{}' pw: '{}'",
|
||||
dek, &invalid_password
|
||||
));
|
||||
if password != "" {
|
||||
let _ = ts
|
||||
.maybe_unprotect_device_encryption_key(&dek_bytes, "")
|
||||
.expect_err(&format!("empty_password: dek: '{}' pw: ''", dek));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
let api = startup().await;
|
||||
let crypto = api.crypto().unwrap();
|
||||
let ts = api.table_store().unwrap();
|
||||
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
let vcrypto = crypto.get(ck).unwrap();
|
||||
test_protect_unprotect(vcrypto.clone(), ts.clone()).await;
|
||||
test_delete_open_delete(ts.clone()).await;
|
||||
test_store_delete_load(ts.clone()).await;
|
||||
test_rkyv(vcrypto.clone(), ts.clone()).await;
|
||||
test_json(vcrypto, ts.clone()).await;
|
||||
let _ = ts.delete("test").await;
|
||||
}
|
||||
|
||||
shutdown(api).await;
|
||||
}
|
40
veilid-core/src/table_store/wasm.rs
Normal file
40
veilid-core/src/table_store/wasm.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use super::*;
|
||||
pub use keyvaluedb_web::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TableStoreDriver {
|
||||
_config: VeilidConfig,
|
||||
}
|
||||
|
||||
impl TableStoreDriver {
|
||||
pub(crate) fn new(config: VeilidConfig) -> Self {
|
||||
Self { _config: config }
|
||||
}
|
||||
|
||||
pub async fn open(&self, table_name: &str, column_count: u32) -> VeilidAPIResult<Database> {
|
||||
let db = Database::open(table_name, column_count, false)
|
||||
.await
|
||||
.map_err(VeilidAPIError::generic)?;
|
||||
trace!(
|
||||
"opened table store '{}' with {} columns",
|
||||
table_name,
|
||||
column_count
|
||||
);
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Delete a TableDB table by name
|
||||
pub async fn delete(&self, table_name: &str) -> VeilidAPIResult<bool> {
|
||||
if is_browser() {
|
||||
let out = Database::delete(table_name).await.is_ok();
|
||||
if out {
|
||||
trace!("TableStore::delete {} deleted", table_name);
|
||||
} else {
|
||||
debug!("TableStore::delete {} not deleted", table_name);
|
||||
}
|
||||
Ok(out)
|
||||
} else {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user