use std::path::PathBuf;
use crate::native::bignum::BigInt;
use crate::native::core::ChoiceValue;
pub trait TestCaseDatabase: Send + Sync {
fn fetch(&self, key: &[u8]) -> Vec<Vec<u8>>;
fn save(&self, key: &[u8], value: &[u8]);
fn delete(&self, key: &[u8], value: &[u8]);
fn move_value(&self, src: &[u8], dst: &[u8], value: &[u8]);
}
pub const METAKEYS_NAME: &[u8] = b".hegel-keys";
const KEY_PREFIX: &[u8] = b"native:";
fn key_hash(key: &[u8]) -> String {
let mut buf = Vec::with_capacity(KEY_PREFIX.len() + key.len());
buf.extend_from_slice(KEY_PREFIX);
buf.extend_from_slice(key);
fnv_hex(&buf)
}
pub struct DirectoryTestCaseDatabase {
db_root: PathBuf,
metakeys_hash: String,
}
impl DirectoryTestCaseDatabase {
pub fn new(db_root: &str) -> Self {
DirectoryTestCaseDatabase {
db_root: PathBuf::from(db_root),
metakeys_hash: key_hash(METAKEYS_NAME),
}
}
pub fn key_path(&self, key: &[u8]) -> PathBuf {
self.db_root.join(key_hash(key))
}
fn value_path(&self, key: &[u8], value: &[u8]) -> PathBuf {
self.key_path(key).join(fnv_hex(value))
}
}
impl TestCaseDatabase for DirectoryTestCaseDatabase {
fn fetch(&self, key: &[u8]) -> Vec<Vec<u8>> {
let dir = self.key_path(key);
let entries = match std::fs::read_dir(&dir) {
Ok(d) => d,
Err(_) => return Vec::new(),
};
let mut out = Vec::new();
for entry in entries.flatten() {
if let Ok(bytes) = std::fs::read(entry.path()) {
out.push(bytes);
}
}
out
}
fn save(&self, key: &[u8], value: &[u8]) {
if key_hash(key) != self.metakeys_hash {
self.save(METAKEYS_NAME, key);
}
let dir = self.key_path(key);
if std::fs::create_dir_all(&dir).is_err() {
return;
}
let path = self.value_path(key, value);
if path.exists() {
return;
}
let _ = std::fs::write(&path, value);
}
fn delete(&self, key: &[u8], value: &[u8]) {
if std::fs::remove_file(self.value_path(key, value)).is_err() {
return;
}
if std::fs::remove_dir(self.key_path(key)).is_ok() && key_hash(key) != self.metakeys_hash {
self.delete(METAKEYS_NAME, key);
}
}
fn move_value(&self, src: &[u8], dst: &[u8], value: &[u8]) {
if src == dst {
self.save(src, value);
return;
}
if !self.key_path(dst).exists() {
self.save(METAKEYS_NAME, dst);
}
let dst_dir = self.key_path(dst);
if std::fs::create_dir_all(&dst_dir).is_err() {
self.delete(src, value);
self.save(dst, value);
return;
}
let src_path = self.value_path(src, value);
let dst_path = self.value_path(dst, value);
if std::fs::rename(&src_path, &dst_path).is_err() {
self.delete(src, value);
self.save(dst, value);
return;
}
let _ = std::fs::remove_dir(self.key_path(src));
}
}
pub(super) fn fnv_hex(s: &[u8]) -> String {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for &byte in s {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
format!("{hash:016x}")
}
pub fn serialize_choices(choices: &[ChoiceValue]) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + choices.len() * 17);
let count = choices.len() as u32;
buf.extend_from_slice(&count.to_le_bytes());
for choice in choices {
match choice {
ChoiceValue::Integer(v) => {
buf.push(0);
serialize_any_integer(&mut buf, v);
}
ChoiceValue::Boolean(v) => {
buf.push(1);
buf.push(*v as u8);
}
ChoiceValue::Float(v) => {
buf.push(2);
buf.extend_from_slice(&v.to_bits().to_le_bytes());
}
ChoiceValue::Bytes(v) => {
buf.push(3);
let len = v.len() as u32;
buf.extend_from_slice(&len.to_le_bytes());
buf.extend_from_slice(v);
}
ChoiceValue::String(v) => {
buf.push(4);
let len = v.len() as u32;
buf.extend_from_slice(&len.to_le_bytes());
for &cp in v {
buf.extend_from_slice(&cp.to_le_bytes());
}
}
}
}
buf
}
fn serialize_any_integer(buf: &mut Vec<u8>, v: &BigInt) {
buf.push(10);
let mag = v.to_signed_bytes_le();
buf.extend_from_slice(&(mag.len() as u32).to_le_bytes());
buf.extend_from_slice(&mag);
}
fn deserialize_any_integer(bytes: &[u8], pos: usize) -> Option<(BigInt, usize)> {
let sub = *bytes.get(pos)?;
let mut pos = pos + 1;
macro_rules! native {
($t:ty) => {{
const N: usize = std::mem::size_of::<$t>();
let raw: [u8; N] = bytes.get(pos..pos + N)?.try_into().ok()?;
pos += N;
BigInt::from(<$t>::from_le_bytes(raw))
}};
}
let value = match sub {
0 => native!(i8),
1 => native!(i16),
2 => native!(i32),
3 => native!(i64),
4 => native!(i128),
5 => native!(u8),
6 => native!(u16),
7 => native!(u32),
8 => native!(u64),
9 => native!(u128),
10 => {
let len_raw: [u8; 4] = bytes.get(pos..pos + 4)?.try_into().ok()?;
pos += 4;
let len = u32::from_le_bytes(len_raw) as usize;
let mag = bytes.get(pos..pos + len)?;
pos += len;
BigInt::from_signed_bytes_le(mag)
}
_ => return None,
};
Some((value, pos))
}
pub fn deserialize_choices(bytes: &[u8]) -> Option<Vec<ChoiceValue>> {
if bytes.len() < 4 {
return None;
}
let count = u32::from_le_bytes(bytes[..4].try_into().ok()?) as usize;
let mut choices = Vec::with_capacity(count.min(bytes.len()));
let mut pos = 4;
for _ in 0..count {
if pos >= bytes.len() {
return None;
}
match bytes[pos] {
0 => {
pos += 1;
let (value, new_pos) = deserialize_any_integer(bytes, pos)?;
pos = new_pos;
choices.push(ChoiceValue::Integer(value));
}
1 => {
pos += 1;
if pos >= bytes.len() {
return None;
}
choices.push(ChoiceValue::Boolean(bytes[pos] != 0));
pos += 1;
}
2 => {
pos += 1;
if pos + 8 > bytes.len() {
return None;
}
let bits = u64::from_le_bytes(bytes[pos..pos + 8].try_into().ok()?);
choices.push(ChoiceValue::Float(f64::from_bits(bits)));
pos += 8;
}
3 => {
pos += 1;
if pos + 4 > bytes.len() {
return None;
}
let len = u32::from_le_bytes(bytes[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
if pos + len > bytes.len() {
return None;
}
choices.push(ChoiceValue::Bytes(bytes[pos..pos + len].to_vec()));
pos += len;
}
4 => {
pos += 1;
if pos + 4 > bytes.len() {
return None;
}
let len = u32::from_le_bytes(bytes[pos..pos + 4].try_into().ok()?) as usize;
pos += 4;
if pos + len.checked_mul(4)? > bytes.len() {
return None;
}
let mut cps = Vec::with_capacity(len);
for _ in 0..len {
let cp = u32::from_le_bytes(bytes[pos..pos + 4].try_into().ok()?);
cps.push(cp);
pos += 4;
}
choices.push(ChoiceValue::String(cps));
}
_ => return None,
}
}
Some(choices)
}
#[cfg(test)]
#[path = "../../tests/embedded/native/database_tests.rs"]
mod tests;