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