mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-01-25 22:15:59 -05:00
checkpoint
This commit is contained in:
parent
f54a6fcf31
commit
a1f295da78
@ -78,7 +78,7 @@ impl ServicesContext {
|
|||||||
|
|
||||||
// Set up tablestore
|
// Set up tablestore
|
||||||
trace!("init table store");
|
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 {
|
if let Err(e) = table_store.init().await {
|
||||||
error!("failed to init table store: {}", e);
|
error!("failed to init table store: {}", e);
|
||||||
self.shutdown().await;
|
self.shutdown().await;
|
||||||
|
@ -169,7 +169,10 @@ impl Crypto {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// load caches if they are valid for this node id
|
// 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? {
|
let caches_valid = match db.load(0, b"cache_validity_key").await? {
|
||||||
Some(v) => v == cache_validity_key,
|
Some(v) => v == cache_validity_key,
|
||||||
None => false,
|
None => false,
|
||||||
|
@ -84,7 +84,7 @@ impl CryptoSystem for CryptoSystemNONE {
|
|||||||
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
||||||
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
||||||
bytes.resize(len as usize, 0u8);
|
bytes.resize(len as usize, 0u8);
|
||||||
random_bytes(bytes.as_mut()).unwrap();
|
random_bytes(bytes.as_mut());
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
fn default_salt_length(&self) -> u32 {
|
fn default_salt_length(&self) -> u32 {
|
||||||
|
@ -53,8 +53,10 @@ pub type TypedKey = CryptoTyped<PublicKey>;
|
|||||||
pub type TypedSecret = CryptoTyped<SecretKey>;
|
pub type TypedSecret = CryptoTyped<SecretKey>;
|
||||||
pub type TypedKeyPair = CryptoTyped<KeyPair>;
|
pub type TypedKeyPair = CryptoTyped<KeyPair>;
|
||||||
pub type TypedSignature = CryptoTyped<Signature>;
|
pub type TypedSignature = CryptoTyped<Signature>;
|
||||||
|
pub type TypedSharedSecret = CryptoTyped<SharedSecret>;
|
||||||
|
|
||||||
pub type TypedKeySet = CryptoTypedSet<PublicKey>;
|
pub type TypedKeySet = CryptoTypedSet<PublicKey>;
|
||||||
pub type TypedSecretSet = CryptoTypedSet<SecretKey>;
|
pub type TypedSecretSet = CryptoTypedSet<SecretKey>;
|
||||||
pub type TypedKeyPairSet = CryptoTypedSet<KeyPair>;
|
pub type TypedKeyPairSet = CryptoTypedSet<KeyPair>;
|
||||||
pub type TypedSignatureSet = CryptoTypedSet<Signature>;
|
pub type TypedSignatureSet = CryptoTypedSet<Signature>;
|
||||||
|
pub type TypedSharedSecretSet = CryptoTypedSet<SharedSecret>;
|
||||||
|
@ -78,7 +78,7 @@ impl CryptoSystem for CryptoSystemVLD0 {
|
|||||||
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
fn random_bytes(&self, len: u32) -> Vec<u8> {
|
||||||
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
let mut bytes = Vec::<u8>::with_capacity(len as usize);
|
||||||
bytes.resize(len as usize, 0u8);
|
bytes.resize(len as usize, 0u8);
|
||||||
random_bytes(bytes.as_mut()).unwrap();
|
random_bytes(bytes.as_mut());
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
fn default_salt_length(&self) -> u32 {
|
fn default_salt_length(&self) -> u32 {
|
||||||
@ -134,12 +134,12 @@ impl CryptoSystem for CryptoSystemVLD0 {
|
|||||||
|
|
||||||
fn random_nonce(&self) -> Nonce {
|
fn random_nonce(&self) -> Nonce {
|
||||||
let mut nonce = [0u8; NONCE_LENGTH];
|
let mut nonce = [0u8; NONCE_LENGTH];
|
||||||
random_bytes(&mut nonce).unwrap();
|
random_bytes(&mut nonce);
|
||||||
Nonce::new(nonce)
|
Nonce::new(nonce)
|
||||||
}
|
}
|
||||||
fn random_shared_secret(&self) -> SharedSecret {
|
fn random_shared_secret(&self) -> SharedSecret {
|
||||||
let mut s = [0u8; SHARED_SECRET_LENGTH];
|
let mut s = [0u8; SHARED_SECRET_LENGTH];
|
||||||
random_bytes(&mut s).unwrap();
|
random_bytes(&mut s);
|
||||||
SharedSecret::new(s)
|
SharedSecret::new(s)
|
||||||
}
|
}
|
||||||
fn compute_dh(
|
fn compute_dh(
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
mod table_db;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@ -10,15 +9,5 @@ mod native;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use native::*;
|
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] =
|
pub static KNOWN_PROTECTED_STORE_KEYS: [&'static str; 4] =
|
||||||
["node_id", "node_id_secret", "_test_key", "RouteSpecStore"];
|
["node_id", "node_id_secret", "_test_key", "RouteSpecStore"];
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
mod block_store;
|
mod block_store;
|
||||||
mod protected_store;
|
mod protected_store;
|
||||||
mod system;
|
mod system;
|
||||||
mod table_store;
|
|
||||||
|
|
||||||
pub use block_store::*;
|
pub use block_store::*;
|
||||||
pub use protected_store::*;
|
pub use protected_store::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
pub use table_store::*;
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub mod android;
|
pub mod android;
|
||||||
|
@ -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<String, Weak<TableDBUnlockedInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Veilid Table Storage
|
|
||||||
/// Database for storing key value pairs persistently across runs
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TableStore {
|
|
||||||
config: VeilidConfig,
|
|
||||||
inner: Arc<Mutex<TableStoreInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<PathBuf> {
|
|
||||||
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<String> {
|
|
||||||
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<TableDB> {
|
|
||||||
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<bool> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,9 @@
|
|||||||
mod block_store;
|
mod block_store;
|
||||||
mod protected_store;
|
mod protected_store;
|
||||||
mod system;
|
mod system;
|
||||||
mod table_store;
|
|
||||||
|
|
||||||
pub use block_store::*;
|
pub use block_store::*;
|
||||||
pub use protected_store::*;
|
pub use protected_store::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
pub use table_store::*;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -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<String, Weak<TableDBUnlockedInner>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TableStore {
|
|
||||||
config: VeilidConfig,
|
|
||||||
inner: Arc<Mutex<TableStoreInner>>,
|
|
||||||
async_lock: Arc<AsyncMutex<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String> {
|
|
||||||
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<TableDB> {
|
|
||||||
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<bool> {
|
|
||||||
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!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,6 +29,7 @@ mod receipt_manager;
|
|||||||
mod routing_table;
|
mod routing_table;
|
||||||
mod rpc_processor;
|
mod rpc_processor;
|
||||||
mod storage_manager;
|
mod storage_manager;
|
||||||
|
mod table_store;
|
||||||
mod veilid_api;
|
mod veilid_api;
|
||||||
mod veilid_config;
|
mod veilid_config;
|
||||||
mod veilid_layer_filter;
|
mod veilid_layer_filter;
|
||||||
|
@ -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 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 let Some(own_node_info_ts) = own_node_info_ts {
|
||||||
if !e.has_seen_our_node_info_ts(routing_domain, 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!(
|
debug!(
|
||||||
"!has_seen_our_node_info_ts: {} own_node_info_ts={}",
|
"!has_seen_our_node_info_ts: {} own_node_info_ts={}",
|
||||||
e.best_node_id(),
|
e.best_node_id(),
|
||||||
|
15
veilid-core/src/table_store/mod.rs
Normal file
15
veilid-core/src/table_store/mod.rs
Normal file
@ -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::*;
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ pub struct TableDBUnlockedInner {
|
|||||||
table: String,
|
table: String,
|
||||||
table_store: TableStore,
|
table_store: TableStore,
|
||||||
database: Database,
|
database: Database,
|
||||||
|
encryption_key: Option<TypedSharedSecret>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for TableDBUnlockedInner {
|
impl fmt::Debug for TableDBUnlockedInner {
|
||||||
@ -34,12 +35,18 @@ pub struct TableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<TypedSharedSecret>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
unlocked_inner: Arc::new(TableDBUnlockedInner {
|
unlocked_inner: Arc::new(TableDBUnlockedInner {
|
||||||
table,
|
table,
|
||||||
table_store,
|
table_store,
|
||||||
database,
|
database,
|
||||||
|
encryption_key,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
305
veilid-core/src/table_store/table_store.rs
Normal file
305
veilid-core/src/table_store/table_store.rs
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<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(crate) async fn init(&self) -> EyreResult<()> {
|
||||||
|
let _async_guard = self.async_lock.lock().await;
|
||||||
|
|
||||||
|
let encryption_key: Option<TypedSharedSecret> = 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<TableDB> {
|
||||||
|
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<bool> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,9 +20,9 @@ pub use core::str::FromStr;
|
|||||||
pub use crypto::*;
|
pub use crypto::*;
|
||||||
pub use intf::BlockStore;
|
pub use intf::BlockStore;
|
||||||
pub use intf::ProtectedStore;
|
pub use intf::ProtectedStore;
|
||||||
pub use intf::{TableDB, TableDBTransaction, TableStore};
|
|
||||||
pub use network_manager::NetworkManager;
|
pub use network_manager::NetworkManager;
|
||||||
pub use routing_table::{NodeRef, NodeRefBase};
|
pub use routing_table::{NodeRef, NodeRefBase};
|
||||||
|
pub use table_store::{TableDB, TableDBTransaction, TableStore};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
@ -16,13 +16,12 @@ impl RngCore for VeilidRng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||||
if let Err(e) = self.try_fill_bytes(dest) {
|
random_bytes(dest);
|
||||||
panic!("Error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
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")] {
|
if #[cfg(target_arch = "wasm32")] {
|
||||||
use js_sys::Math;
|
use js_sys::Math;
|
||||||
|
|
||||||
pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> {
|
pub fn random_bytes(dest: &mut [u8]) {
|
||||||
let len = dest.len();
|
let len = dest.len();
|
||||||
let u32len = len / 4;
|
let u32len = len / 4;
|
||||||
let remlen = len % 4;
|
let remlen = len % 4;
|
||||||
@ -49,8 +48,6 @@ cfg_if! {
|
|||||||
dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8;
|
dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_random_u32() -> u32 {
|
pub fn get_random_u32() -> u32 {
|
||||||
@ -65,9 +62,9 @@ cfg_if! {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> {
|
pub fn random_bytes(dest: &mut [u8]) {
|
||||||
let mut rng = rand::thread_rng();
|
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 {
|
pub fn get_random_u32() -> u32 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user