diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index 6bce23a4..52983521 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -78,7 +78,7 @@ impl ServicesContext { // Set up tablestore trace!("init table store"); - let table_store = TableStore::new(self.config.clone()); + let table_store = TableStore::new(self.config.clone(), protected_store.clone()); if let Err(e) = table_store.init().await { error!("failed to init table store: {}", e); self.shutdown().await; diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index 15eb43e4..c2026237 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -169,7 +169,10 @@ impl Crypto { }; // load caches if they are valid for this node id - let mut db = table_store.open("crypto_caches", 1).await?; + let mut db = table_store + .open("crypto_caches", 1) + .await + .wrap_err("failed to open crypto_caches")?; let caches_valid = match db.load(0, b"cache_validity_key").await? { Some(v) => v == cache_validity_key, None => false, diff --git a/veilid-core/src/crypto/none/mod.rs b/veilid-core/src/crypto/none/mod.rs index 7a2ac32f..69ae1377 100644 --- a/veilid-core/src/crypto/none/mod.rs +++ b/veilid-core/src/crypto/none/mod.rs @@ -84,7 +84,7 @@ impl CryptoSystem for CryptoSystemNONE { fn random_bytes(&self, len: u32) -> Vec { let mut bytes = Vec::::with_capacity(len as usize); bytes.resize(len as usize, 0u8); - random_bytes(bytes.as_mut()).unwrap(); + random_bytes(bytes.as_mut()); bytes } fn default_salt_length(&self) -> u32 { diff --git a/veilid-core/src/crypto/types/mod.rs b/veilid-core/src/crypto/types/mod.rs index 8b8de7de..1e22829a 100644 --- a/veilid-core/src/crypto/types/mod.rs +++ b/veilid-core/src/crypto/types/mod.rs @@ -53,8 +53,10 @@ pub type TypedKey = CryptoTyped; pub type TypedSecret = CryptoTyped; pub type TypedKeyPair = CryptoTyped; pub type TypedSignature = CryptoTyped; +pub type TypedSharedSecret = CryptoTyped; pub type TypedKeySet = CryptoTypedSet; pub type TypedSecretSet = CryptoTypedSet; pub type TypedKeyPairSet = CryptoTypedSet; pub type TypedSignatureSet = CryptoTypedSet; +pub type TypedSharedSecretSet = CryptoTypedSet; diff --git a/veilid-core/src/crypto/vld0/mod.rs b/veilid-core/src/crypto/vld0/mod.rs index 1ead81c7..41e6c640 100644 --- a/veilid-core/src/crypto/vld0/mod.rs +++ b/veilid-core/src/crypto/vld0/mod.rs @@ -78,7 +78,7 @@ impl CryptoSystem for CryptoSystemVLD0 { fn random_bytes(&self, len: u32) -> Vec { let mut bytes = Vec::::with_capacity(len as usize); bytes.resize(len as usize, 0u8); - random_bytes(bytes.as_mut()).unwrap(); + random_bytes(bytes.as_mut()); bytes } fn default_salt_length(&self) -> u32 { @@ -134,12 +134,12 @@ impl CryptoSystem for CryptoSystemVLD0 { fn random_nonce(&self) -> Nonce { let mut nonce = [0u8; NONCE_LENGTH]; - random_bytes(&mut nonce).unwrap(); + random_bytes(&mut nonce); Nonce::new(nonce) } fn random_shared_secret(&self) -> SharedSecret { let mut s = [0u8; SHARED_SECRET_LENGTH]; - random_bytes(&mut s).unwrap(); + random_bytes(&mut s); SharedSecret::new(s) } fn compute_dh( diff --git a/veilid-core/src/intf/mod.rs b/veilid-core/src/intf/mod.rs index 6c1ae9f3..ab9d5759 100644 --- a/veilid-core/src/intf/mod.rs +++ b/veilid-core/src/intf/mod.rs @@ -1,4 +1,3 @@ -mod table_db; use super::*; #[cfg(target_arch = "wasm32")] @@ -10,15 +9,5 @@ mod native; #[cfg(not(target_arch = "wasm32"))] pub use native::*; -pub static KNOWN_TABLE_NAMES: [&'static str; 7] = [ - "crypto_caches", - "RouteSpecStore", - "routing_table", - "local_records", - "local_subkeys", - "remote_records", - "remote_subkeys", -]; - pub static KNOWN_PROTECTED_STORE_KEYS: [&'static str; 4] = ["node_id", "node_id_secret", "_test_key", "RouteSpecStore"]; diff --git a/veilid-core/src/intf/native/mod.rs b/veilid-core/src/intf/native/mod.rs index 6f2b8791..018cba41 100644 --- a/veilid-core/src/intf/native/mod.rs +++ b/veilid-core/src/intf/native/mod.rs @@ -1,12 +1,10 @@ mod block_store; mod protected_store; mod system; -mod table_store; pub use block_store::*; pub use protected_store::*; pub use system::*; -pub use table_store::*; #[cfg(target_os = "android")] pub mod android; diff --git a/veilid-core/src/intf/native/table_store.rs b/veilid-core/src/intf/native/table_store.rs deleted file mode 100644 index 5200c12f..00000000 --- a/veilid-core/src/intf/native/table_store.rs +++ /dev/null @@ -1,148 +0,0 @@ -use super::*; -use crate::intf::table_db::TableDBUnlockedInner; -pub use crate::intf::table_db::{TableDB, TableDBTransaction}; -use keyvaluedb_sqlite::*; -use std::path::PathBuf; - -struct TableStoreInner { - opened: BTreeMap>, -} - -/// Veilid Table Storage -/// Database for storing key value pairs persistently across runs -#[derive(Clone)] -pub struct TableStore { - config: VeilidConfig, - inner: Arc>, -} - -impl TableStore { - fn new_inner() -> TableStoreInner { - TableStoreInner { - opened: BTreeMap::new(), - } - } - pub(crate) fn new(config: VeilidConfig) -> Self { - Self { - config, - inner: Arc::new(Mutex::new(Self::new_inner())), - } - } - - /// Delete all known tables - pub async fn delete_all(&self) { - for ktn in &KNOWN_TABLE_NAMES { - if let Err(e) = self.delete(ktn).await { - error!("failed to delete '{}': {}", ktn, e); - } else { - debug!("deleted table '{}'", ktn); - } - } - } - - pub(crate) async fn init(&self) -> EyreResult<()> { - Ok(()) - } - - pub(crate) async fn terminate(&self) { - let inner = self.inner.lock(); - if !inner.opened.is_empty() { - panic!( - "all open databases should have been closed: {:?}", - inner.opened - ); - } - } - - 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"); - } - } - - fn get_dbpath(&self, table: &str) -> EyreResult { - if !table - .chars() - .all(|c| char::is_alphanumeric(c) || c == '_' || c == '-') - { - bail!("table name '{}' is invalid", table); - } - let c = self.config.get(); - let tablestoredir = c.table_store.directory.clone(); - std::fs::create_dir_all(&tablestoredir).wrap_err("failed to create tablestore path")?; - - let dbpath: PathBuf = [tablestoredir, String::from(table)].iter().collect(); - Ok(dbpath) - } - - fn get_table_name(&self, table: &str) -> EyreResult { - if !table - .chars() - .all(|c| char::is_alphanumeric(c) || c == '_' || c == '-') - { - bail!("table name '{}' is invalid", table); - } - let c = self.config.get(); - let namespace = c.namespace.clone(); - Ok(if namespace.is_empty() { - table.to_string() - } else { - format!("_ns_{}_{}", namespace, table) - }) - } - - /// 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) -> EyreResult { - let table_name = self.get_table_name(name)?; - - 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); - } - }; - } - - let dbpath = self.get_dbpath(&table_name)?; - - // Ensure permissions are correct - ensure_file_private_owner(&dbpath)?; - - let cfg = DatabaseConfig::with_columns(column_count); - let db = Database::open(&dbpath, cfg).wrap_err("failed to open tabledb")?; - - // Ensure permissions are correct - ensure_file_private_owner(&dbpath)?; - - trace!( - "opened table store '{}' at path '{:?}' with {} columns", - name, - dbpath, - column_count - ); - let table_db = TableDB::new(table_name.clone(), self.clone(), db); - - inner.opened.insert(table_name, table_db.weak_inner()); - - Ok(table_db) - } - - /// Delete a TableDB table by name - pub async fn delete(&self, name: &str) -> EyreResult { - let table_name = self.get_table_name(name)?; - - let inner = self.inner.lock(); - if inner.opened.contains_key(&table_name) { - bail!("Not deleting table that is still opened"); - } - let dbpath = self.get_dbpath(&table_name)?; - let ret = std::fs::remove_file(dbpath).is_ok(); - Ok(ret) - } -} diff --git a/veilid-core/src/intf/wasm/mod.rs b/veilid-core/src/intf/wasm/mod.rs index a2b0f374..b69ada7b 100644 --- a/veilid-core/src/intf/wasm/mod.rs +++ b/veilid-core/src/intf/wasm/mod.rs @@ -1,11 +1,9 @@ mod block_store; mod protected_store; mod system; -mod table_store; pub use block_store::*; pub use protected_store::*; pub use system::*; -pub use table_store::*; use super::*; diff --git a/veilid-core/src/intf/wasm/table_store.rs b/veilid-core/src/intf/wasm/table_store.rs deleted file mode 100644 index 6828b3bf..00000000 --- a/veilid-core/src/intf/wasm/table_store.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::*; -use crate::intf::table_db::TableDBUnlockedInner; -pub use crate::intf::table_db::{TableDB, TableDBTransaction}; -use keyvaluedb_web::*; - -struct TableStoreInner { - opened: BTreeMap>, -} - -#[derive(Clone)] -pub struct TableStore { - config: VeilidConfig, - inner: Arc>, - async_lock: Arc>, -} - -impl TableStore { - fn new_inner() -> TableStoreInner { - TableStoreInner { - opened: BTreeMap::new(), - } - } - pub(crate) fn new(config: VeilidConfig) -> Self { - Self { - config, - inner: Arc::new(Mutex::new(Self::new_inner())), - async_lock: Arc::new(AsyncMutex::new(())), - } - } - - /// Delete all known tables - pub async fn delete_all(&self) { - for ktn in &KNOWN_TABLE_NAMES { - if let Err(e) = self.delete(ktn).await { - error!("failed to delete '{}': {}", ktn, e); - } - } - } - - pub(crate) async fn init(&self) -> EyreResult<()> { - let _async_guard = self.async_lock.lock().await; - Ok(()) - } - - pub(crate) async fn terminate(&self) { - let _async_guard = self.async_lock.lock().await; - assert!( - self.inner.lock().opened.len() == 0, - "all open databases should have been closed" - ); - } - - pub(crate) fn on_table_db_drop(&self, table: String) { - let mut inner = self.inner.lock(); - match inner.opened.remove(&table) { - Some(_) => (), - None => { - assert!(false, "should have removed an item"); - } - } - } - - fn get_table_name(&self, table: &str) -> EyreResult { - if !table - .chars() - .all(|c| char::is_alphanumeric(c) || c == '_' || c == '-') - { - bail!("table name '{}' is invalid", table); - } - let c = self.config.get(); - let namespace = c.namespace.clone(); - Ok(if namespace.len() == 0 { - format!("{}", table) - } else { - format!("_ns_{}_{}", namespace, table) - }) - } - - /// 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) -> EyreResult { - let _async_guard = self.async_lock.lock().await; - let table_name = self.get_table_name(name)?; - - { - 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); - } - }; - } - } - let db = Database::open(table_name.clone(), column_count, false) - .await - .wrap_err("failed to open tabledb")?; - trace!( - "opened table store '{}' with table name '{:?}' with {} columns", - name, - table_name, - column_count - ); - - let table_db = TableDB::new(table_name.clone(), self.clone(), db); - - { - let mut inner = self.inner.lock(); - inner.opened.insert(table_name, table_db.weak_inner()); - } - - Ok(table_db) - } - - /// Delete a TableDB table by name - pub async fn delete(&self, name: &str) -> EyreResult { - let _async_guard = self.async_lock.lock().await; - trace!("TableStore::delete {}", name); - let table_name = self.get_table_name(name)?; - - { - let inner = self.inner.lock(); - if inner.opened.contains_key(&table_name) { - trace!( - "TableStore::delete {}: Not deleting, still open.", - table_name - ); - bail!("Not deleting table that is still opened"); - } - } - - if is_browser() { - let out = match Database::delete(table_name.clone()).await { - Ok(_) => true, - Err(_) => false, - }; - //.map_err(|e| format!("failed to delete tabledb at: {} ({})", table_name, e))?; - trace!("TableStore::deleted {}", table_name); - Ok(out) - } else { - unimplemented!(); - } - } -} diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index b3381a5b..bca8a6fe 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -29,6 +29,7 @@ mod receipt_manager; mod routing_table; mod rpc_processor; mod storage_manager; +mod table_store; mod veilid_api; mod veilid_config; mod veilid_layer_filter; diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 409f2a0c..d66f7ed0 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -563,7 +563,7 @@ impl RoutingTableInner { // If we need a ping because this node hasn't seen our latest node info, then do it if let Some(own_node_info_ts) = own_node_info_ts { if !e.has_seen_our_node_info_ts(routing_domain, own_node_info_ts) { - //xxx remove this when we fix + //xxx remove this when we fix #208 debug!( "!has_seen_our_node_info_ts: {} own_node_info_ts={}", e.best_node_id(), diff --git a/veilid-core/src/table_store/mod.rs b/veilid-core/src/table_store/mod.rs new file mode 100644 index 00000000..16ab5483 --- /dev/null +++ b/veilid-core/src/table_store/mod.rs @@ -0,0 +1,15 @@ +use super::*; + +mod table_db; +mod table_store; +pub use table_db::*; +pub use table_store::*; + +#[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::*; diff --git a/veilid-core/src/table_store/native.rs b/veilid-core/src/table_store/native.rs new file mode 100644 index 00000000..9e1186d5 --- /dev/null +++ b/veilid-core/src/table_store/native.rs @@ -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 { + 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 { + 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 { + let dbpath = self.get_dbpath(&table_name)?; + if !dbpath.exists() { + return Ok(false); + } + std::fs::remove_file(dbpath).map_err(VeilidAPIError::from)?; + Ok(true) + } +} diff --git a/veilid-core/src/intf/table_db.rs b/veilid-core/src/table_store/table_db.rs similarity index 97% rename from veilid-core/src/intf/table_db.rs rename to veilid-core/src/table_store/table_db.rs index 05ebdf8b..cfa8cba3 100644 --- a/veilid-core/src/intf/table_db.rs +++ b/veilid-core/src/table_store/table_db.rs @@ -14,6 +14,7 @@ pub struct TableDBUnlockedInner { table: String, table_store: TableStore, database: Database, + encryption_key: Option, } impl fmt::Debug for TableDBUnlockedInner { @@ -34,12 +35,18 @@ pub struct TableDB { } impl TableDB { - pub(super) fn new(table: String, table_store: TableStore, database: Database) -> Self { + pub(super) fn new( + table: String, + table_store: TableStore, + database: Database, + encryption_key: Option, + ) -> Self { Self { unlocked_inner: Arc::new(TableDBUnlockedInner { table, table_store, database, + encryption_key, }), } } diff --git a/veilid-core/src/table_store/table_store.rs b/veilid-core/src/table_store/table_store.rs new file mode 100644 index 00000000..d035bac8 --- /dev/null +++ b/veilid-core/src/table_store/table_store.rs @@ -0,0 +1,305 @@ +use super::*; +use keyvaluedb::*; + +struct TableStoreInner { + opened: BTreeMap>, + encryption_key: Option, + all_table_names: HashMap, + all_tables_db: Option, +} + +/// 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>, // Sync mutex here because TableDB drops can happen at any time + async_lock: Arc>, // 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, + } + } + 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(())), + } + } + + // Flush internal control state + 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); + } + } xxx must from_rkyv the all_table_names + + // Internal naming support + // Adds rename capability and ensures names of tables are totally unique and valid + + fn namespaced_name(&self, table: &str) -> VeilidAPIResult { + 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 { + 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> { + 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> { + 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::>(); + 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(crate) async fn init(&self) -> EyreResult<()> { + let _async_guard = self.async_lock.lock().await; + + let encryption_key: Option = self + .protected_store + .load_user_secret_rkyv("device_encryption_key") + .await?; + + let all_tables_db = self + .table_store_driver + .open("__veilid_all_tables", 1) + .await + .wrap_err("failed to create all tables table")?; + + { + let mut inner = self.inner.lock(); + inner.encryption_key = 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; + 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.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 { + let _async_guard = self.async_lock.lock().await; + 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(), + db, + 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 { + let _async_guard = self.async_lock.lock().await; + 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; + trace!("TableStore::rename {} -> {}", old_name, new_name); + self.name_rename(old_name, new_name).await + } +} diff --git a/veilid-core/src/table_store/wasm.rs b/veilid-core/src/table_store/wasm.rs new file mode 100644 index 00000000..71b2b4fa --- /dev/null +++ b/veilid-core/src/table_store/wasm.rs @@ -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 { + 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 { + 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!(); + } + } +} diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 8cc28366..eb4cb312 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -20,9 +20,9 @@ pub use core::str::FromStr; pub use crypto::*; pub use intf::BlockStore; pub use intf::ProtectedStore; -pub use intf::{TableDB, TableDBTransaction, TableStore}; pub use network_manager::NetworkManager; pub use routing_table::{NodeRef, NodeRefBase}; +pub use table_store::{TableDB, TableDBTransaction, TableStore}; use crate::*; use core::fmt; diff --git a/veilid-tools/src/random.rs b/veilid-tools/src/random.rs index 9c3d9fa7..0ac52be3 100644 --- a/veilid-tools/src/random.rs +++ b/veilid-tools/src/random.rs @@ -16,13 +16,12 @@ impl RngCore for VeilidRng { } fn fill_bytes(&mut self, dest: &mut [u8]) { - if let Err(e) = self.try_fill_bytes(dest) { - panic!("Error: {}", e); - } + random_bytes(dest); } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - random_bytes(dest).map_err(rand::Error::new) + random_bytes(dest); + Ok(()) } } @@ -30,7 +29,7 @@ cfg_if! { if #[cfg(target_arch = "wasm32")] { use js_sys::Math; - pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { + pub fn random_bytes(dest: &mut [u8]) { let len = dest.len(); let u32len = len / 4; let remlen = len % 4; @@ -49,8 +48,6 @@ cfg_if! { dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8; } } - - Ok(()) } pub fn get_random_u32() -> u32 { @@ -65,9 +62,9 @@ cfg_if! { } else { - pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { + pub fn random_bytes(dest: &mut [u8]) { let mut rng = rand::thread_rng(); - rng.try_fill_bytes(dest).wrap_err("failed to fill bytes") + rng.fill_bytes(dest); } pub fn get_random_u32() -> u32 {