use core::fmt;
use std::collections::BTreeSet;
use crate::container::{Descriptor, ObjectSource};
use crate::error::{Error, Result};
mod io;
pub use io::{IoCounters, IoSnapshot};
#[cfg(feature = "store")]
mod embedded;
#[cfg(feature = "store")]
pub use embedded::{EmbeddedStore, STORE_FORMAT_VERSION, STORE_MAGIC};
#[cfg(feature = "store")]
mod account;
#[cfg(feature = "store")]
pub use account::{AccountReport, RootAccount, account};
#[cfg(feature = "entropyfs-store")]
mod entropyfs;
#[cfg(feature = "entropyfs-store")]
pub use entropyfs::EntropyFsStore;
#[cfg(all(feature = "entropyfs-store", feature = "field"))]
pub(crate) use entropyfs::map_engine_error;
#[cfg(feature = "field")]
mod seed;
#[cfg(feature = "field")]
pub use seed::{
FsSeedStore, NodeId, SEED_FORMAT_VERSION, SEED_NODE_DOMAIN, SeedStore, SeedStoreStats,
closure as seed_closure,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct StoreStats {
pub object_count: u64,
pub total_bytes: u64,
pub stored_bytes: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GcReport {
pub reachable: u64,
pub swept: u64,
pub bytes_reclaimed: u64,
pub dangling: Vec<Id>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Id([u8; 32]);
impl Id {
pub const fn from_bytes(b: [u8; 32]) -> Self {
Id(b)
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[cfg(feature = "store")]
pub fn of(bytes: &[u8]) -> Self {
Id(*blake3::hash(bytes).as_bytes())
}
pub fn to_hex(&self) -> String {
crate::integrity::to_hex(&self.0)
}
pub fn from_hex(s: &str) -> Result<Self> {
let raw = s.as_bytes();
if raw.len() != 64 {
return Err(Error::usage(format!(
"object id must be 64 hex characters, got {}",
raw.len()
)));
}
let nib = |c: u8| -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
};
let mut out = [0u8; 32];
for (i, byte) in out.iter_mut().enumerate() {
let hi =
nib(raw[2 * i]).ok_or_else(|| Error::usage("object id has a non-hex character"))?;
let lo = nib(raw[2 * i + 1])
.ok_or_else(|| Error::usage("object id has a non-hex character"))?;
*byte = (hi << 4) | lo;
}
Ok(Id(out))
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
pub trait ObjectStore {
fn put(&mut self, bytes: &[u8]) -> Result<Id>;
fn get(&self, id: &Id) -> Result<Vec<u8>>;
fn get_range(&self, id: &Id, offset: u64, len: u64) -> Result<Vec<u8>>;
fn contains(&self, id: &Id) -> Result<bool> {
let _ = id;
Err(Error::unsupported_feature(
"contains is not implemented by this backend",
))
}
fn list(&self) -> Result<Vec<(Id, u64)>> {
Err(Error::unsupported_feature(
"object enumeration is not implemented by this backend",
))
}
fn remove(&self, id: &Id) -> Result<u64> {
let _ = id;
Err(Error::unsupported_feature(
"per-object delete is not implemented by this backend",
))
}
}
pub trait ObjectResolver {
fn get(&self, id: &Id, len: u64) -> Result<Vec<u8>>;
}
impl<T: ObjectStore> ObjectResolver for T {
fn get(&self, id: &Id, len: u64) -> Result<Vec<u8>> {
let bytes = ObjectStore::get(self, id)?;
if bytes.len() as u64 != len {
return Err(Error::integrity_mismatch(format!(
"external object {id} has {} bytes, EXTERNAL_REF declared {len}",
bytes.len()
)));
}
Ok(bytes)
}
}
pub struct NullResolver;
impl ObjectResolver for NullResolver {
fn get(&self, id: &Id, _len: u64) -> Result<Vec<u8>> {
Err(Error::missing_external_object(format!(
"no store supplied to resolve object {id}"
)))
}
}
pub fn externalize<R: ObjectResolver + ?Sized>(
d: &mut Descriptor,
resolver: &R,
store: &mut impl ObjectStore,
) -> Result<()> {
let mut out: Vec<ObjectSource> = Vec::with_capacity(d.objects.len());
for src in &d.objects {
let bytes: Vec<u8> = match src {
ObjectSource::Inline(bytes) => bytes.clone(),
ObjectSource::External { id, len } => resolver.get(id, *len)?,
};
let len = bytes.len() as u64;
let id = store.put(&bytes)?;
out.push(ObjectSource::External { id, len });
}
d.objects = out;
Ok(())
}
pub fn hydrate<R: ObjectResolver>(d: &mut Descriptor, resolver: &R) -> Result<()> {
let mut out: Vec<ObjectSource> = Vec::with_capacity(d.objects.len());
for src in &d.objects {
match src {
ObjectSource::Inline(bytes) => out.push(ObjectSource::Inline(bytes.clone())),
ObjectSource::External { id, len } => {
out.push(ObjectSource::Inline(resolver.get(id, *len)?));
}
}
}
d.objects = out;
Ok(())
}
pub fn gc<R: ObjectResolver + ObjectStore>(roots: &[Descriptor], store: &R) -> Result<GcReport> {
let mut mark: BTreeSet<Id> = BTreeSet::new();
for root in roots {
for src in &root.objects {
if let ObjectSource::External { id, .. } = src {
mark.insert(*id);
}
}
}
let stored = store.list()?;
let stored_ids: BTreeSet<Id> = stored.iter().map(|(id, _)| *id).collect();
let mut dangling: Vec<Id> = mark
.iter()
.filter(|id| !stored_ids.contains(id))
.copied()
.collect();
dangling.sort_unstable();
let mut swept: u64 = 0;
let mut bytes_reclaimed: u64 = 0;
for (id, _len) in &stored {
if !mark.contains(id) {
bytes_reclaimed += store.remove(id)?;
swept += 1;
}
}
Ok(GcReport {
reachable: mark.len() as u64,
swept,
bytes_reclaimed,
dangling,
})
}