mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2025-06-11 02:43:20 +02:00
Restructure
This commit is contained in:
58
bffhd/db/fix.rs
Normal file
58
bffhd/db/fix.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::{
|
||||
ptr::NonNull,
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::db::Transaction;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// Memory Fixpoint for a value in the DB
|
||||
///
|
||||
/// LMDB binds lifetimes of buffers to the transaction that returned the buffer. As long as this
|
||||
/// transaction is not `commit()`ed, `abort()`ed or `reset()`ed the pages containing these values
|
||||
/// are not returned into circulation.
|
||||
/// This struct encodes this by binding a live reference to the Transaction to the returned
|
||||
/// and interpreted buffer. The placeholder `T` is the container for the transaction. This may be a
|
||||
/// plain `RoTransaction<'env>`, a `Rc<RoTxn>` (meaning Fix is !Send) or an `Arc<RoTxn>`, depending
|
||||
/// on your needs.
|
||||
pub struct LMDBorrow<T, V> {
|
||||
ptr: NonNull<V>,
|
||||
txn: T,
|
||||
}
|
||||
|
||||
impl<'env, T, V> LMDBorrow<T, V>
|
||||
where T: Transaction,
|
||||
{
|
||||
pub unsafe fn new(ptr: NonNull<V>, txn: T) -> Self {
|
||||
Self { ptr: ptr.into(), txn }
|
||||
}
|
||||
|
||||
pub fn unwrap_txn(self) -> T {
|
||||
self.txn
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T, V> Deref for LMDBorrow<T, V>
|
||||
{
|
||||
type Target = V;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// As long as the transaction is kept alive (which it is, because it's in self) state is a
|
||||
// valid pointer so this is safe.
|
||||
unsafe { self.ptr.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T, V: Debug> Debug for LMDBorrow<T, V> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T, V: serde::Serialize> serde::Serialize for LMDBorrow<T, V> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: serde::Serializer
|
||||
{
|
||||
self.deref().serialize(serializer)
|
||||
}
|
||||
}
|
164
bffhd/db/hash.rs
Normal file
164
bffhd/db/hash.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
hash::{
|
||||
Hash,
|
||||
Hasher,
|
||||
BuildHasher,
|
||||
},
|
||||
collections::hash_map::RandomState,
|
||||
};
|
||||
|
||||
use rkyv::{
|
||||
Archive,
|
||||
Archived,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Fallible,
|
||||
};
|
||||
|
||||
use super::{
|
||||
DB,
|
||||
Adapter,
|
||||
OutputBuffer,
|
||||
|
||||
Environment,
|
||||
|
||||
DatabaseFlags,
|
||||
WriteFlags,
|
||||
|
||||
Transaction,
|
||||
RwTransaction,
|
||||
};
|
||||
|
||||
|
||||
#[derive(Archive, Serialize, Deserialize)]
|
||||
/// The entry as it is stored inside the database.
|
||||
pub struct Entry<K: Archive, V: Archive> {
|
||||
pub key: K,
|
||||
pub val: V,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HashAdapter<K, A> {
|
||||
k: PhantomData<K>,
|
||||
a: PhantomData<A>,
|
||||
}
|
||||
impl<K, A> HashAdapter<K, A> {
|
||||
pub fn new() -> Self {
|
||||
Self { k: PhantomData, a: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, A: Fallible> Fallible for HashAdapter<K, A> { type Error = <A as Fallible>::Error; }
|
||||
impl<K, A: Adapter> Adapter for HashAdapter<K, A>
|
||||
where K: Archive,
|
||||
Entry<K, A::Value>: Serialize<A::Serializer>,
|
||||
{
|
||||
type Serializer = A::Serializer;
|
||||
type Value = Entry<K, A::Value>;
|
||||
|
||||
fn new_serializer() -> Self::Serializer
|
||||
{ A::new_serializer() }
|
||||
|
||||
fn from_ser_err(e: <Self::Serializer as Fallible>::Error) -> <A as Fallible>::Error
|
||||
{ A::from_ser_err(e) }
|
||||
|
||||
fn from_db_err(e: lmdb::Error) -> <A as Fallible>::Error
|
||||
{ A::from_db_err(e) }
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HASH_FLAGS: libc::c_uint =
|
||||
DatabaseFlags::INTEGER_KEY.bits() + DatabaseFlags::DUP_SORT.bits();
|
||||
|
||||
pub struct HashDB<A, K, H = RandomState>
|
||||
{
|
||||
db: DB<HashAdapter<K, A>>,
|
||||
hash_builder: H,
|
||||
}
|
||||
|
||||
impl<A, K> HashDB<A, K>
|
||||
{
|
||||
pub unsafe fn create(env: &Environment, name: Option<&str>) -> lmdb::Result<Self> {
|
||||
Self::create_with_hasher(env, name, RandomState::new())
|
||||
}
|
||||
pub unsafe fn open(env: &Environment, name: Option<&str>) -> lmdb::Result<Self> {
|
||||
Self::open_with_hasher(env, name, RandomState::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K, H: BuildHasher> HashDB<A, K, H>
|
||||
{
|
||||
fn new(db: DB<HashAdapter<K, A>>, hash_builder: H) -> Self {
|
||||
Self { db, hash_builder }
|
||||
}
|
||||
|
||||
pub unsafe fn create_with_hasher(env: &Environment, name: Option<&str>, hash_builder: H)
|
||||
-> lmdb::Result<Self>
|
||||
{
|
||||
let flags = DatabaseFlags::from_bits(DEFAULT_HASH_FLAGS).unwrap();
|
||||
DB::create(env, name, flags).map(|db| Self::new(db, hash_builder))
|
||||
}
|
||||
pub unsafe fn open_with_hasher(env: &Environment, name: Option<&str>, hash_builder: H)
|
||||
-> lmdb::Result<Self>
|
||||
{
|
||||
DB::open(env, name).map(|db| Self::new(db, hash_builder))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<A, K, H> HashDB<A, K, H>
|
||||
where A: Adapter,
|
||||
HashAdapter<K, A>: Adapter<Value=Entry<K, A::Value>>,
|
||||
H: BuildHasher,
|
||||
K: Hash + Archive,
|
||||
K::Archived: PartialEq<K>,
|
||||
{
|
||||
/// Retrieve an entry from the hashdb
|
||||
///
|
||||
/// The result is a view pinned to the lifetime of the transaction. You can get owned Values
|
||||
/// using [`Deserialize`].
|
||||
pub fn get<'txn, T: Transaction>(&self, txn: &'txn T, key: &K)
|
||||
-> Result<
|
||||
Option<&'txn Archived<<HashAdapter<K, A> as Adapter>::Value>>,
|
||||
<HashAdapter<K, A> as Fallible>::Error
|
||||
>
|
||||
{
|
||||
let mut hasher = self.hash_builder.build_hasher();
|
||||
key.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
let mut cursor = self.db.open_ro_cursor(txn)?;
|
||||
let i = cursor
|
||||
.iter_dup_of(&hash.to_ne_bytes()).filter_map(|r| r.ok())
|
||||
.map(|(_keybuf, entry)| entry);
|
||||
for entry in i {
|
||||
let entry: &Archived<Entry<K, A::Value>> = entry;
|
||||
if entry.key == *key {
|
||||
return Ok(Some(entry));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A, K, H> HashDB<A, K, H>
|
||||
where A: Adapter,
|
||||
A::Serializer: OutputBuffer,
|
||||
H: BuildHasher,
|
||||
K: Hash + Serialize<A::Serializer>,
|
||||
K::Archived: PartialEq<K>,
|
||||
{
|
||||
pub fn insert_entry(&self, txn: &mut RwTransaction, entry: &Entry<K, A::Value>)
|
||||
-> Result<(), A::Error>
|
||||
{
|
||||
let mut hasher = self.hash_builder.build_hasher();
|
||||
entry.key.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
self.db.put(txn, &hash.to_ne_bytes(), entry, WriteFlags::empty())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
73
bffhd/db/pass.rs
Normal file
73
bffhd/db/pass.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use std::sync::Arc;
|
||||
use super::Environment;
|
||||
use super::AllocAdapter;
|
||||
use super::DB;
|
||||
use super::raw::RawDB;
|
||||
use super::{DatabaseFlags, WriteFlags};
|
||||
use crate::db::Result;
|
||||
use super::Transaction;
|
||||
|
||||
use argon2;
|
||||
|
||||
type Adapter = AllocAdapter<String>;
|
||||
#[derive(Clone)]
|
||||
pub struct PassDB {
|
||||
env: Arc<Environment>,
|
||||
db: DB<Adapter>,
|
||||
}
|
||||
|
||||
impl PassDB {
|
||||
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
|
||||
let db = DB::new_unchecked(db);
|
||||
Self { env, db }
|
||||
}
|
||||
|
||||
pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
|
||||
let db = RawDB::open(&env, Some("pass"))?;
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub unsafe fn create(env: Arc<Environment>) -> Result<Self> {
|
||||
let flags = DatabaseFlags::empty();
|
||||
let db = RawDB::create(&env, Some("pass"), flags)?;
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub fn check_pw<P: AsRef<[u8]>>(&self, uid: &str, inpass: P) -> Result<Option<bool>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
if let Some(pass) = self.db.get(&txn, &uid.as_bytes())? {
|
||||
Ok(argon2::verify_encoded(pass.as_str(), inpass.as_ref())
|
||||
.ok())
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_password<P: AsRef<[u8]>>(&self, uid: &str, password: P) -> Result<()> {
|
||||
let cfg = argon2::Config::default();
|
||||
let salt: [u8; 10] = rand::random();
|
||||
let enc = argon2::hash_encoded(password.as_ref(), &salt, &cfg)
|
||||
.expect("Hashing password failed for static valid config");
|
||||
|
||||
let flags = WriteFlags::empty();
|
||||
let mut txn = self.env.begin_rw_txn()?;
|
||||
self.db.put(&mut txn, &uid.as_bytes(), &enc, flags)?;
|
||||
txn.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_all(&self) -> Result<Vec<(String, String)>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
let mut cursor = self.db.open_ro_cursor(&txn)?;
|
||||
let iter = cursor.iter_start();
|
||||
let mut out = Vec::new();
|
||||
for pass in iter {
|
||||
let (uid, pass) = pass?;
|
||||
let uid = unsafe { std::str::from_utf8_unchecked(uid).to_string() };
|
||||
let pass = pass.as_str().to_string();
|
||||
out.push((uid, pass));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
62
bffhd/db/raw.rs
Normal file
62
bffhd/db/raw.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use lmdb::{
|
||||
Transaction,
|
||||
RwTransaction,
|
||||
Environment,
|
||||
DatabaseFlags,
|
||||
WriteFlags,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawDB {
|
||||
db: lmdb::Database,
|
||||
}
|
||||
|
||||
impl RawDB {
|
||||
pub fn open(env: &Environment, name: Option<&str>) -> lmdb::Result<Self> {
|
||||
env.open_db(name).map(|db| Self { db })
|
||||
}
|
||||
|
||||
pub fn create(env: &Environment, name: Option<&str>, flags: DatabaseFlags) -> lmdb::Result<Self> {
|
||||
env.create_db(name, flags).map(|db| Self { db })
|
||||
}
|
||||
|
||||
pub fn get<'txn, T: Transaction, K>(&self, txn: &'txn T, key: &K) -> lmdb::Result<Option<&'txn [u8]>>
|
||||
where K: AsRef<[u8]>
|
||||
{
|
||||
match txn.get(self.db, key) {
|
||||
Ok(buf) => Ok(Some(buf)),
|
||||
Err(lmdb::Error::NotFound) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put<K, V>(&self, txn: &mut RwTransaction, key: &K, value: &V, flags: WriteFlags)
|
||||
-> lmdb::Result<()>
|
||||
where K: AsRef<[u8]>,
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
txn.put(self.db, key, value, flags)
|
||||
}
|
||||
|
||||
pub fn reserve<'txn, K>(&self, txn: &'txn mut RwTransaction, key: &K, size: usize, flags: WriteFlags)
|
||||
-> lmdb::Result<&'txn mut [u8]>
|
||||
where K: AsRef<[u8]>
|
||||
{
|
||||
txn.reserve(self.db, key, size, flags)
|
||||
}
|
||||
|
||||
pub fn del<K, V>(&self, txn: &mut RwTransaction, key: &K, value: Option<&V>) -> lmdb::Result<()>
|
||||
where K: AsRef<[u8]>,
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
txn.del(self.db, key, value.map(AsRef::as_ref))
|
||||
}
|
||||
|
||||
pub fn iter<'txn, C: lmdb::Cursor<'txn>>(&self, cursor: &'txn mut C) -> lmdb::Iter<'txn> {
|
||||
cursor.iter_start()
|
||||
}
|
||||
|
||||
pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T) -> lmdb::Result<lmdb::RoCursor<'txn>> {
|
||||
txn.open_ro_cursor(self.db)
|
||||
}
|
||||
}
|
72
bffhd/db/resources.rs
Normal file
72
bffhd/db/resources.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use rkyv::{Archive, Serialize, Deserialize};
|
||||
|
||||
use super::{
|
||||
DB,
|
||||
};
|
||||
use crate::db::{AlignedAdapter, AllocAdapter};
|
||||
use crate::db::raw::RawDB;
|
||||
use std::sync::Arc;
|
||||
use crate::db::{Environment, DatabaseFlags};
|
||||
use crate::db::Result;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Archive, Serialize, Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Resource {
|
||||
uuid: u128,
|
||||
id: String,
|
||||
name_idx: u64,
|
||||
description_idx: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceDB {
|
||||
env: Arc<Environment>,
|
||||
db: DB<AllocAdapter<Resource>>,
|
||||
id_index: DB<AlignedAdapter<u64>>,
|
||||
}
|
||||
|
||||
impl ResourceDB {
|
||||
pub unsafe fn new(env: Arc<Environment>, db: RawDB, id_index: RawDB) -> Self {
|
||||
let db = DB::new_unchecked(db);
|
||||
let id_index = DB::new_unchecked(id_index);
|
||||
|
||||
Self { env, db, id_index }
|
||||
}
|
||||
|
||||
pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
|
||||
let db = RawDB::open(&env, Some("resources"))?;
|
||||
let idx = RawDB::open(&env, Some("resources-idx"))?;
|
||||
Ok(Self::new(env, db, idx))
|
||||
}
|
||||
|
||||
pub unsafe fn create(env: Arc<Environment>) -> Result<Self> {
|
||||
let flags = DatabaseFlags::empty();
|
||||
let db = RawDB::create(&env, Some("resources"), flags)?;
|
||||
let idx = RawDB::create(&env, Some("resources-idx"), flags)?;
|
||||
Ok(Self::new(env, db, idx))
|
||||
}
|
||||
|
||||
pub fn lookup_id<S: AsRef<str>>(&self, id: S) -> Result<Option<u64>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
let id = self.id_index.get(&txn, &id.as_ref().as_bytes()).map(|ok| {
|
||||
ok.map(|num| *num)
|
||||
})?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn get_all(&self) -> Result<Vec<(String, u64)>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
let mut cursor = self.id_index.open_ro_cursor(&txn)?;
|
||||
let iter = cursor.iter_start();
|
||||
let mut out = Vec::new();
|
||||
|
||||
for id in iter {
|
||||
let (name, id) = id?;
|
||||
let name = unsafe { std::str::from_utf8_unchecked(name).to_string() };
|
||||
out.push((name, *id));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
195
bffhd/db/state.rs
Normal file
195
bffhd/db/state.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use std::{
|
||||
sync::Arc,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use rkyv::{Archived};
|
||||
|
||||
use super::{
|
||||
DB,
|
||||
Environment,
|
||||
|
||||
EnvironmentFlags,
|
||||
DatabaseFlags,
|
||||
WriteFlags,
|
||||
|
||||
Adapter,
|
||||
AllocAdapter,
|
||||
DBError,
|
||||
|
||||
Transaction,
|
||||
RoTransaction,
|
||||
RwTransaction,
|
||||
|
||||
LMDBorrow,
|
||||
};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
type StateAdapter = AllocAdapter<State>;
|
||||
|
||||
/// State Database containing the currently set state
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StateDB {
|
||||
/// The environment for all the databases below
|
||||
env: Arc<Environment>,
|
||||
|
||||
input: DB<StateAdapter>,
|
||||
output: DB<StateAdapter>,
|
||||
|
||||
// TODO: Index resource name/id/uuid -> u64
|
||||
}
|
||||
|
||||
impl StateDB {
|
||||
fn open_env<P: AsRef<Path>>(path: P) -> lmdb::Result<Environment> {
|
||||
Environment::new()
|
||||
.set_flags( EnvironmentFlags::WRITE_MAP
|
||||
| EnvironmentFlags::NO_SUB_DIR
|
||||
| EnvironmentFlags::NO_TLS
|
||||
| EnvironmentFlags::NO_READAHEAD)
|
||||
.set_max_dbs(2)
|
||||
.open(path.as_ref())
|
||||
}
|
||||
|
||||
fn new(env: Environment, input: DB<StateAdapter>, output: DB<StateAdapter>) -> Self {
|
||||
Self { env: Arc::new(env), input, output }
|
||||
}
|
||||
|
||||
pub fn init<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
let env = Self::open_env(path)?;
|
||||
let input = unsafe {
|
||||
DB::create(&env, Some("input"), DatabaseFlags::INTEGER_KEY)?
|
||||
};
|
||||
let output = unsafe {
|
||||
DB::create(&env, Some("output"), DatabaseFlags::INTEGER_KEY)?
|
||||
};
|
||||
|
||||
Ok(Self::new(env, input, output))
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
let env = Self::open_env(path)?;
|
||||
let input = unsafe { DB::open(&env, Some("input"))? };
|
||||
let output = unsafe { DB::open(&env, Some("output"))? };
|
||||
|
||||
Ok(Self::new(env, input, output))
|
||||
}
|
||||
|
||||
pub fn create<P: AsRef<Path>>(path: P) -> lmdb::Result<Self> {
|
||||
let flags = DatabaseFlags::empty();
|
||||
let env = Self::open_env(path)?;
|
||||
let input = unsafe { DB::create(&env, Some("input"), flags)? };
|
||||
let output = unsafe { DB::create(&env, Some("output"), flags)? };
|
||||
|
||||
Ok(Self::new(env, input, output))
|
||||
}
|
||||
|
||||
fn update_txn(&self, txn: &mut RwTransaction, key: u64, input: &State, output: &State)
|
||||
-> Result<(), DBError>
|
||||
{
|
||||
let flags = WriteFlags::empty();
|
||||
let k = key.to_ne_bytes();
|
||||
self.input.put(txn, &k, input, flags)?;
|
||||
self.output.put(txn, &k, output, flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&self, key: u64, input: &State, output: &State)
|
||||
-> Result<(), DBError>
|
||||
{
|
||||
let mut txn = self.env.begin_rw_txn().map_err(StateAdapter::from_db_err)?;
|
||||
self.update_txn(&mut txn, key, input, output)?;
|
||||
|
||||
txn.commit().map_err(StateAdapter::from_db_err)
|
||||
}
|
||||
|
||||
fn get(&self, db: &DB<StateAdapter>, key: u64)
|
||||
-> Result<Option<LMDBorrow<RoTransaction, Archived<State>>>, DBError>
|
||||
{
|
||||
let txn = self.env.begin_ro_txn().map_err(StateAdapter::from_db_err)?;
|
||||
if let Some(state) = db.get(&txn, &key.to_ne_bytes())? {
|
||||
let ptr = state.into();
|
||||
Ok(Some(unsafe { LMDBorrow::new(ptr, txn) }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_input(&self, key: u64)
|
||||
-> Result<Option<LMDBorrow<RoTransaction, Archived<State>>>, DBError>
|
||||
{ self.get(&self.input, key) }
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_output(&self, key: u64)
|
||||
-> Result<Option<LMDBorrow<RoTransaction, Archived<State>>>, DBError>
|
||||
{ self.get(&self.output, key) }
|
||||
|
||||
pub fn accessor(&self, key: u64) -> StateAccessor {
|
||||
StateAccessor::new(key, self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StateAccessor {
|
||||
key: u64,
|
||||
db: StateDB
|
||||
}
|
||||
|
||||
impl StateAccessor {
|
||||
pub fn new(key: u64, db: StateDB) -> Self {
|
||||
Self { key, db }
|
||||
}
|
||||
|
||||
pub fn get_input(&self)
|
||||
-> Result<Option<LMDBorrow<RoTransaction, Archived<State>>>, DBError>
|
||||
{
|
||||
self.db.get_input(self.key)
|
||||
}
|
||||
|
||||
pub fn get_output(&self)
|
||||
-> Result<Option<LMDBorrow<RoTransaction, Archived<State>>>, DBError>
|
||||
{
|
||||
self.db.get_output(self.key)
|
||||
}
|
||||
|
||||
pub fn set(&self, input: &State, output: &State) -> Result<(), DBError> {
|
||||
self.db.update(self.key, input, output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::state::value::Vec3u8;
|
||||
use crate::state::value::{OID_COLOUR, OID_POWERED, OID_INTENSITY};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[test]
|
||||
fn construct_state() {
|
||||
let tmpdir = tempfile::tempdir().unwrap();
|
||||
let mut tmppath = tmpdir.path().to_owned();
|
||||
tmppath.push("db");
|
||||
let db = StateDB::init(tmppath).unwrap();
|
||||
let b = State::build()
|
||||
.add(OID_COLOUR.clone(), Box::new(Vec3u8 { a: 1, b: 2, c: 3}))
|
||||
.add(OID_POWERED.clone(), Box::new(true))
|
||||
.add(OID_INTENSITY.clone(), Box::new(1023))
|
||||
.finish();
|
||||
println!("({}) {:?}", b.hash(), b);
|
||||
|
||||
let c = State::build()
|
||||
.add(OID_COLOUR.clone(), Box::new(Vec3u8 { a: 1, b: 2, c: 3}))
|
||||
.add(OID_POWERED.clone(), Box::new(true))
|
||||
.add(OID_INTENSITY.clone(), Box::new(1023))
|
||||
.finish();
|
||||
|
||||
let key = rand::random();
|
||||
db.update(key, &b, &c).unwrap();
|
||||
let d = db.get_input(key).unwrap().unwrap();
|
||||
let e = db.get_output(key).unwrap().unwrap();
|
||||
assert_eq!(&b, d.deref());
|
||||
assert_eq!(&c, e.deref());
|
||||
}
|
||||
}
|
242
bffhd/db/typed.rs
Normal file
242
bffhd/db/typed.rs
Normal file
@ -0,0 +1,242 @@
|
||||
use std::{
|
||||
fmt,
|
||||
any::type_name,
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use rkyv::{
|
||||
Archived,
|
||||
archived_root,
|
||||
|
||||
Serialize,
|
||||
|
||||
ser::{
|
||||
Serializer,
|
||||
serializers::AllocSerializer,
|
||||
},
|
||||
|
||||
util::AlignedVec,
|
||||
|
||||
Fallible,
|
||||
};
|
||||
|
||||
use lmdb::{
|
||||
Environment,
|
||||
DatabaseFlags,
|
||||
WriteFlags,
|
||||
|
||||
Transaction,
|
||||
RwTransaction,
|
||||
};
|
||||
|
||||
use super::RawDB;
|
||||
|
||||
pub trait Adapter: Fallible {
|
||||
type Serializer: rkyv::ser::Serializer;
|
||||
type Value: Serialize<Self::Serializer>;
|
||||
|
||||
fn new_serializer() -> Self::Serializer;
|
||||
|
||||
fn from_ser_err(e: <Self::Serializer as Fallible>::Error) -> <Self as Fallible>::Error;
|
||||
fn from_db_err(e: lmdb::Error) -> <Self as Fallible>::Error;
|
||||
}
|
||||
|
||||
struct AdapterPrettyPrinter<A: Adapter>(PhantomData<A>);
|
||||
|
||||
impl<A: Adapter> AdapterPrettyPrinter<A> {
|
||||
pub fn new() -> Self { Self(PhantomData) }
|
||||
}
|
||||
|
||||
impl<A: Adapter> fmt::Debug for AdapterPrettyPrinter<A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(&type_name::<A>())
|
||||
.field("serializer", &type_name::<A::Serializer>())
|
||||
.field("value", &type_name::<A::Value>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OutputBuffer {
|
||||
type Buffer: AsRef<[u8]>;
|
||||
fn into_slice(self) -> Self::Buffer;
|
||||
}
|
||||
|
||||
impl<const N: usize> OutputBuffer for AllocSerializer<N> {
|
||||
type Buffer = AlignedVec;
|
||||
fn into_slice(self) -> Self::Buffer {
|
||||
self.into_serializer().into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should be possible to autoimplement for Sized Serializers
|
||||
pub trait OutputWriter: Fallible {
|
||||
fn write_into(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
pub struct DB<A> {
|
||||
db: RawDB,
|
||||
phantom: PhantomData<A>,
|
||||
}
|
||||
impl<A> Clone for DB<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
db: self.db.clone(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<A: Adapter> fmt::Debug for DB<A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DB")
|
||||
.field("db", &self.db)
|
||||
.field("adapter", &AdapterPrettyPrinter::<A>::new())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> DB<A> {
|
||||
pub unsafe fn new_unchecked(db: RawDB) -> Self {
|
||||
Self { db, phantom: PhantomData }
|
||||
}
|
||||
|
||||
fn new(db: RawDB) -> Self {
|
||||
unsafe { Self::new_unchecked(db) }
|
||||
}
|
||||
|
||||
/// Open the underlying DB, creating it if necessary
|
||||
///
|
||||
/// This function is unsafe since if the DB does not contain `A::Archived` we may end up doing
|
||||
/// random memory reads or writes
|
||||
pub unsafe fn create(env: &Environment, name: Option<&str>, flags: DatabaseFlags)
|
||||
-> lmdb::Result<Self>
|
||||
{
|
||||
RawDB::create(env, name, flags).map(Self::new)
|
||||
}
|
||||
|
||||
/// Open the underlying DB
|
||||
///
|
||||
/// This function is unsafe since if the DB does not contain `A::Archived` we may end up doing
|
||||
/// random memory reads or writes
|
||||
pub unsafe fn open(env: &Environment, name: Option<&str>) -> lmdb::Result<Self> {
|
||||
RawDB::open(env, name).map(Self::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Adapter> DB<A>
|
||||
{
|
||||
pub fn del<K: AsRef<[u8]>>(&self, txn: &mut RwTransaction, key: &K) -> Result<(), A::Error> {
|
||||
let v: Option<&Vec<u8>> = None;
|
||||
self.db.del(txn, key, v).map_err(A::from_db_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Adapter> DB<A>
|
||||
{
|
||||
pub fn get<'txn, T: Transaction, K: AsRef<[u8]>>(&self, txn: &'txn T, key: &K)
|
||||
-> Result<Option<&'txn Archived<A::Value>>, A::Error>
|
||||
{
|
||||
if let Some(buf) = self.db.get(txn, key).map_err(A::from_db_err)? {
|
||||
Ok(Some(unsafe { archived_root::<A::Value>(buf.as_ref()) }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_ro_cursor<'txn, T: Transaction>(&self, txn: &'txn T)
|
||||
-> Result<TypedCursor<lmdb::RoCursor<'txn>, A>, A::Error>
|
||||
{
|
||||
let c = self.db.open_ro_cursor(txn)
|
||||
.map_err(A::from_db_err)?;
|
||||
// Safe because we are providing both Adapter and cursor and know it matches
|
||||
Ok(unsafe { TypedCursor::new(c) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A> DB<A>
|
||||
where A: Adapter,
|
||||
A::Serializer: OutputBuffer,
|
||||
{
|
||||
pub fn put<K: AsRef<[u8]>>(&self, txn: &mut RwTransaction, key: &K, val: &A::Value, flags: WriteFlags)
|
||||
-> Result<usize, A::Error>
|
||||
{
|
||||
let mut serializer = A::new_serializer();
|
||||
let pos = serializer.serialize_value(val)
|
||||
.map_err(A::from_ser_err)?;
|
||||
|
||||
let buf = serializer.into_slice();
|
||||
self.db.put(txn, key, &buf, flags)
|
||||
.map_err(A::from_db_err)?;
|
||||
|
||||
Ok(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A> DB<A>
|
||||
where A: Adapter,
|
||||
A::Serializer: OutputWriter,
|
||||
{
|
||||
pub fn put_nocopy<K: AsRef<[u8]>>(&self, txn: &mut RwTransaction, key: &K, val: &A::Value, flags: WriteFlags)
|
||||
-> Result<usize, A::Error>
|
||||
{
|
||||
let mut serializer = A::new_serializer();
|
||||
let pos = serializer.serialize_value(val)
|
||||
.map_err(A::from_ser_err)?;
|
||||
|
||||
let mut buf = self.db.reserve(txn, &key.as_ref(), pos, flags)
|
||||
.map_err(A::from_db_err)?;
|
||||
serializer.write_into(&mut buf)
|
||||
.map_err(A::from_ser_err)?;
|
||||
|
||||
Ok(pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypedCursor<C, A> {
|
||||
cursor: C,
|
||||
phantom: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<'txn, C, A> TypedCursor<C, A>
|
||||
where C: lmdb::Cursor<'txn>,
|
||||
A: Adapter,
|
||||
{
|
||||
// Unsafe because we don't know if the given adapter matches the given cursor
|
||||
pub unsafe fn new(cursor: C) -> Self {
|
||||
Self { cursor, phantom: PhantomData }
|
||||
}
|
||||
|
||||
pub fn iter_start(&mut self) -> Iter<'txn, A> {
|
||||
let iter = self.cursor.iter_start();
|
||||
// Safe because `new` isn't :P
|
||||
unsafe { Iter::new(iter) }
|
||||
}
|
||||
|
||||
pub fn iter_dup_of<K: AsRef<[u8]>>(&mut self, key: &K) -> Iter<'txn, A> {
|
||||
let iter = self.cursor.iter_dup_of(key);
|
||||
// Safe because `new` isn't :P
|
||||
unsafe { Iter::new(iter) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'txn, A> {
|
||||
iter: lmdb::Iter<'txn>,
|
||||
phantom: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<'txn, A: Adapter> Iter<'txn, A> {
|
||||
pub unsafe fn new(iter: lmdb::Iter<'txn>) -> Self {
|
||||
Self { iter, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'txn, A: Adapter> Iterator for Iter<'txn, A>
|
||||
where Archived<A::Value>: 'txn
|
||||
{
|
||||
type Item = Result<(&'txn [u8], &'txn Archived<A::Value>), A::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|r| r
|
||||
.map_err(A::from_db_err)
|
||||
.map(|(key, buf)| { (key, unsafe { archived_root::<A::Value>(buf) }) }))
|
||||
}
|
||||
}
|
71
bffhd/db/user.rs
Normal file
71
bffhd/db/user.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::sync::Arc;
|
||||
use super::{DB, AllocAdapter, Environment, Result};
|
||||
use crate::db::raw::RawDB;
|
||||
use crate::db::{DatabaseFlags, LMDBorrow, RoTransaction, WriteFlags, };
|
||||
|
||||
use rkyv::{Archive, Serialize, Deserialize, Archived};
|
||||
|
||||
type Adapter = AllocAdapter<User>;
|
||||
#[derive(Clone)]
|
||||
pub struct UserDB {
|
||||
env: Arc<Environment>,
|
||||
db: DB<Adapter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Archive, Serialize, Deserialize, serde::Serialize, serde::Deserialize)]
|
||||
pub struct User {
|
||||
id: u128,
|
||||
username: String,
|
||||
roles: Vec<String>,
|
||||
}
|
||||
|
||||
impl UserDB {
|
||||
pub unsafe fn new(env: Arc<Environment>, db: RawDB) -> Self {
|
||||
let db = DB::new_unchecked(db);
|
||||
Self { env, db }
|
||||
}
|
||||
|
||||
pub unsafe fn open(env: Arc<Environment>) -> Result<Self> {
|
||||
let db = RawDB::open(&env, Some("user"))?;
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub unsafe fn create(env: Arc<Environment>) -> Result<Self> {
|
||||
let flags = DatabaseFlags::empty();
|
||||
let db = RawDB::create(&env, Some("user"), flags)?;
|
||||
Ok(Self::new(env, db))
|
||||
}
|
||||
|
||||
pub fn get(&self, uid: &str) -> Result<Option<LMDBorrow<RoTransaction, Archived<User>>>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
if let Some(state) = self.db.get(&txn, &uid.as_bytes())? {
|
||||
let ptr = state.into();
|
||||
Ok(Some(unsafe { LMDBorrow::new(ptr, txn) }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&self, uid: &str, user: &User) -> Result<()> {
|
||||
let mut txn = self.env.begin_rw_txn()?;
|
||||
let flags = WriteFlags::empty();
|
||||
self.db.put(&mut txn, &uid.as_bytes(), user, flags)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_all(&self) -> Result<Vec<(String, User)>> {
|
||||
let txn = self.env.begin_ro_txn()?;
|
||||
let mut cursor = self.db.open_ro_cursor(&txn)?;
|
||||
let iter = cursor.iter_start();
|
||||
let mut out = Vec::new();
|
||||
let mut deserializer = rkyv::Infallible;
|
||||
for user in iter {
|
||||
let (uid, user) = user?;
|
||||
let uid = unsafe { std::str::from_utf8_unchecked(uid).to_string() };
|
||||
let user: User = user.deserialize(&mut deserializer).unwrap();
|
||||
out.push((uid, user));
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user