use std::collections::BTreeMap;
use nectar_primitives::bmt::DEFAULT_BODY_SIZE;
use nectar_primitives::chunk::ChunkAddress;
use nectar_primitives::store::{SyncChunkGet, SyncChunkPut};
use crate::entry::Entry;
use crate::mode::NodeEntry;
use crate::node::Node;
use crate::{MantarayError, Result, metadata};
#[derive(Debug)]
pub struct Manifest<S, E: NodeEntry = ChunkAddress, const BS: usize = DEFAULT_BODY_SIZE> {
trie: Node<E>,
store: S,
}
impl<S, const BS: usize> Manifest<S, ChunkAddress, BS> {
pub fn new(store: S) -> Self {
let trie = Node::new_unencrypted();
Self { trie, store }
}
pub fn open(root: ChunkAddress, store: S) -> Self {
let trie = Node::from_reference(root);
Self { trie, store }
}
}
#[cfg(feature = "encryption")]
impl<S, const BS: usize> Manifest<S, nectar_primitives::EncryptedChunkRef, BS> {
pub fn new_encrypted(store: S) -> Self {
use crate::obfuscation::ObfuscationKey;
let trie = Node {
obfuscation_key: ObfuscationKey::generate(),
..Node::default()
};
Self { trie, store }
}
pub fn open_encrypted(root: crate::ManifestRef, store: S) -> Self {
let (addr, key) = root.into_parts();
let mut trie = Node::from_reference(addr);
trie.obfuscation_key = key;
Self { trie, store }
}
}
impl<S, E: NodeEntry, const BS: usize> Manifest<S, E, BS> {
pub const fn store(&self) -> &S {
&self.store
}
pub const fn root(&self) -> &Node<E> {
&self.trie
}
pub const fn root_mut(&mut self) -> &mut Node<E> {
&mut self.trie
}
pub fn into_parts(self) -> (Node<E>, S) {
(self.trie, self.store)
}
pub const fn reference(&self) -> Option<&ChunkAddress> {
self.trie.reference()
}
}
impl<S: SyncChunkGet<BS>, E: NodeEntry, const BS: usize> Manifest<S, E, BS> {
pub fn add(&mut self, path: &str, reference: impl Into<E>) -> Result<()> {
let entry = reference.into();
self.trie
.add::<S, BS>(path.as_bytes(), Some(entry), BTreeMap::new(), &self.store)
}
pub fn add_with_metadata(
&mut self,
path: &str,
reference: impl Into<E>,
metadata: BTreeMap<String, String>,
) -> Result<()> {
let entry = reference.into();
self.trie
.add::<S, BS>(path.as_bytes(), Some(entry), metadata, &self.store)
}
pub fn add_entry(&mut self, path: &str, entry: Entry) -> Result<()> {
let e = match entry.reference {
Some(r) => {
let bytes = Vec::from(&r);
Some(E::try_from_bytes(&bytes)?)
}
None => None,
};
self.trie
.add::<S, BS>(path.as_bytes(), e, entry.metadata, &self.store)
}
pub fn remove(&mut self, path: &str) -> Result<()> {
self.trie.remove::<S, BS>(path.as_bytes(), &self.store)
}
pub fn lookup(&mut self, path: &str) -> Result<Entry> {
let node = self
.trie
.lookup_node::<S, BS>(path.as_bytes(), &self.store)?;
if !node.is_value() {
return Err(MantarayError::NotValueType);
}
Entry::from_node(path.as_bytes(), node)
}
pub fn has_prefix(&mut self, prefix: &str) -> Result<bool> {
self.trie
.has_prefix::<S, BS>(prefix.as_bytes(), &self.store)
}
pub fn walk<F>(&mut self, f: &mut F) -> Result<()>
where
F: FnMut(&[u8], &Node<E>) -> Result<()>,
{
self.trie.walk::<S, BS, _>(&self.store, f)
}
pub fn walk_from<F>(&mut self, root: &str, f: &mut F) -> Result<()>
where
F: FnMut(&[u8], &Node<E>) -> Result<()>,
{
self.trie
.walk_from::<S, BS, _>(root.as_bytes(), &self.store, f)
}
pub fn entries(&mut self) -> Result<Vec<Entry>> {
self.iter().collect()
}
pub fn set_index_document(&mut self, filename: &str) -> Result<()> {
self.set_root_metadata(metadata::WEBSITE_INDEX_DOCUMENT, filename)
}
pub fn set_error_document(&mut self, path: &str) -> Result<()> {
self.set_root_metadata(metadata::WEBSITE_ERROR_DOCUMENT, path)
}
pub fn index_document(&mut self) -> Result<Option<String>> {
self.get_root_metadata(metadata::WEBSITE_INDEX_DOCUMENT)
}
pub fn error_document(&mut self) -> Result<Option<String>> {
self.get_root_metadata(metadata::WEBSITE_ERROR_DOCUMENT)
}
pub fn iterate_addresses<F>(&mut self, mut f: F) -> Result<()>
where
F: FnMut(&[u8]) -> Result<()>,
{
self.walk(&mut |_path, node| {
if let Some(addr) = node.reference() {
f(addr.as_bytes())?;
}
if let Some(entry) = node.entry()
&& node.is_value()
{
let entry_bytes = entry.to_bytes();
f(&entry_bytes)?;
}
Ok(())
})
}
pub const fn iter(&mut self) -> ManifestIter<'_, S, E, BS> {
ManifestIter::new(&mut self.trie, &self.store)
}
fn set_root_metadata(&mut self, key: &str, value: &str) -> Result<()> {
match self
.trie
.lookup_node::<S, BS>(metadata::ROOT_PATH.as_bytes(), &self.store)
{
Ok(node) => {
node.metadata_mut().insert(key.into(), value.into());
node.make_with_metadata();
node.mark_dirty();
Ok(())
}
Err(MantarayError::NoForkFound { .. }) => {
let mut meta = BTreeMap::new();
meta.insert(key.into(), value.into());
self.trie
.add::<S, BS>(metadata::ROOT_PATH.as_bytes(), None, meta, &self.store)
}
Err(e) => Err(e),
}
}
fn get_root_metadata(&mut self, key: &str) -> Result<Option<String>> {
match self
.trie
.lookup_node::<S, BS>(metadata::ROOT_PATH.as_bytes(), &self.store)
{
Ok(node) => Ok(node.metadata().get(key).cloned()),
Err(MantarayError::NoForkFound { .. }) => Ok(None),
Err(e) => Err(e),
}
}
}
impl<S: SyncChunkGet<BS> + SyncChunkPut<BS>, const BS: usize> Manifest<S, ChunkAddress, BS> {
pub fn save(&mut self) -> Result<ChunkAddress> {
self.trie.save::<S, BS>(&self.store)?;
Ok(*self
.trie
.reference()
.ok_or(MantarayError::MissingReference)?)
}
}
#[cfg(feature = "encryption")]
impl<S: SyncChunkGet<BS> + SyncChunkPut<BS>, const BS: usize>
Manifest<S, nectar_primitives::EncryptedChunkRef, BS>
{
pub fn save(&mut self) -> Result<crate::ManifestRef> {
self.trie.save::<S, BS>(&self.store)?;
let addr = *self
.trie
.reference()
.ok_or(MantarayError::MissingReference)?;
Ok(crate::ManifestRef::new(addr, self.trie.obfuscation_key))
}
}
pub struct ManifestIter<'a, S, E: NodeEntry = ChunkAddress, const BS: usize = DEFAULT_BODY_SIZE> {
trie: &'a mut Node<E>,
store: &'a S,
stack: Vec<IterFrame<E>>,
path_buf: Vec<u8>,
root_visited: bool,
}
impl<S, E: NodeEntry, const BS: usize> std::fmt::Debug for ManifestIter<'_, S, E, BS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ManifestIter")
.field("stack_depth", &self.stack.len())
.field("root_visited", &self.root_visited)
.finish_non_exhaustive()
}
}
struct IterFrame<E: NodeEntry> {
node: *mut Node<E>,
path_len_before: usize,
keys: Vec<u8>,
key_idx: usize,
}
impl<'a, S: SyncChunkGet<BS>, E: NodeEntry, const BS: usize> ManifestIter<'a, S, E, BS> {
pub(crate) const fn new(trie: &'a mut Node<E>, store: &'a S) -> Self {
Self {
trie,
store,
stack: Vec::new(),
path_buf: Vec::new(),
root_visited: false,
}
}
}
impl<S: SyncChunkGet<BS>, E: NodeEntry, const BS: usize> Iterator for ManifestIter<'_, S, E, BS> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if !self.root_visited {
self.root_visited = true;
if !self.trie.loaded
&& let Err(e) = self.trie.load::<S, BS>(self.store)
{
return Some(Err(e));
}
let keys: Vec<u8> = self.trie.forks.keys().copied().collect();
let entry = if self.trie.is_value() {
match Entry::from_node(&[], self.trie) {
Ok(e) => Some(e),
Err(e) => return Some(Err(e)),
}
} else {
None
};
self.stack.push(IterFrame {
node: self.trie as *mut Node<E>,
path_len_before: 0,
keys,
key_idx: 0,
});
if let Some(entry) = entry {
return Some(Ok(entry));
}
continue;
}
while self.stack.last().is_some_and(|f| f.key_idx >= f.keys.len()) {
let frame = self.stack.pop().unwrap();
self.path_buf.truncate(frame.path_len_before);
}
let (key, parent_node) = {
let frame = self.stack.last_mut()?;
let key = frame.keys[frame.key_idx];
frame.key_idx += 1;
(key, frame.node)
};
let parent = unsafe { &mut *parent_node };
let fork = match parent.forks.get_mut(&key) {
Some(f) => f,
None => {
return Some(Err(MantarayError::NoForkFound {
reference: parent.reference,
}));
}
};
let child = &mut fork.node as *mut Node<E>;
let child_ref = unsafe { &mut *child };
if !child_ref.loaded
&& let Err(e) = child_ref.load::<S, BS>(self.store)
{
return Some(Err(e));
}
let child_keys: Vec<u8> = child_ref.forks.keys().copied().collect();
let is_value = child_ref.is_value();
let path_len_before = self.path_buf.len();
self.path_buf.extend_from_slice(&fork.prefix);
self.stack.push(IterFrame {
node: child,
path_len_before,
keys: child_keys,
key_idx: 0,
});
if is_value {
match Entry::from_node(&self.path_buf, child_ref) {
Ok(e) => return Some(Ok(e)),
Err(e) => return Some(Err(e)),
}
}
}
}
}
impl<'a, S: SyncChunkGet<BS>, E: NodeEntry, const BS: usize> IntoIterator
for &'a mut Manifest<S, E, BS>
{
type Item = Result<Entry>;
type IntoIter = ManifestIter<'a, S, E, BS>;
fn into_iter(self) -> Self::IntoIter {
ManifestIter::new(&mut self.trie, &self.store)
}
}
#[cfg(test)]
mod tests {
use nectar_primitives::bmt::DEFAULT_BODY_SIZE;
use nectar_primitives::chunk::ChunkAddress;
use nectar_primitives::store::MemoryStore;
type Store = MemoryStore<DEFAULT_BODY_SIZE>;
type PlainManifest<S, const BS: usize = DEFAULT_BODY_SIZE> =
super::Manifest<S, ChunkAddress, BS>;
fn make_addr(s: &str) -> ChunkAddress {
let bytes = s.as_bytes();
let mut buf = [0u8; 32];
let len = bytes.len().min(32);
buf[..len].copy_from_slice(&bytes[..len]);
ChunkAddress::from(buf)
}
fn make_addr_u32(i: u32) -> ChunkAddress {
let mut buf = [0u8; 32];
buf[28..].copy_from_slice(&i.to_be_bytes());
ChunkAddress::from(buf)
}
#[test]
fn persist_idempotence() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &[
"aa", "b", "aaaaaa", "aaaaab", "abbbb", "abbba", "bbbbba", "bbbaaa", "bbbaab",
];
for &path in paths {
m.save().unwrap();
let addr = make_addr(path);
m.add(path, addr).unwrap();
}
m.save().unwrap();
for &path in paths {
let entry = m.lookup(path).unwrap();
let expected = make_addr(path);
assert_eq!(entry.address(), Some(&expected));
}
}
#[test]
fn manifest_entries() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["index.html", "img/1.png", "img/2.png", "robots.txt"];
for &path in paths {
let addr = make_addr(path);
m.add(path, addr).unwrap();
}
let entries = m.entries().unwrap();
assert_eq!(entries.len(), paths.len());
for &path in paths {
assert!(
entries.iter().any(|e| e.path() == path.as_bytes()),
"path {path} not found in entries"
);
}
}
#[test]
fn website_document_helpers() {
let store = Store::new();
let mut m = PlainManifest::new(store);
m.add("/", ChunkAddress::from([0u8; 32])).unwrap();
m.set_index_document("index.html").unwrap();
m.set_error_document("404.html").unwrap();
assert_eq!(m.index_document().unwrap(), Some("index.html".to_string()));
assert_eq!(m.error_document().unwrap(), Some("404.html".to_string()));
}
#[test]
fn website_document_helpers_merge_metadata() {
let store = Store::new();
let mut m = PlainManifest::new(store);
m.set_index_document("index.html").unwrap();
m.set_error_document("404.html").unwrap();
assert_eq!(m.index_document().unwrap(), Some("index.html".to_string()));
assert_eq!(m.error_document().unwrap(), Some("404.html".to_string()));
}
#[test]
fn website_document_helpers_none_when_missing() {
let store = Store::new();
let mut m = PlainManifest::new(store);
assert_eq!(m.index_document().unwrap(), None);
assert_eq!(m.error_document().unwrap(), None);
}
#[test]
fn iterate_addresses_yields_all_refs() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["index.html", "img/1.png", "img/2.png", "robots.txt"];
for &path in paths {
let addr = make_addr(path);
m.add(path, addr).unwrap();
}
let root_ref = m.save().unwrap();
let (_, store) = m.into_parts();
let mut m2 = PlainManifest::open(root_ref, store);
let mut addresses = Vec::new();
m2.iterate_addresses(|addr| {
addresses.push(addr.to_vec());
Ok(())
})
.unwrap();
assert!(!addresses.is_empty());
for &path in paths {
let expected = make_addr(path);
assert!(
addresses.iter().any(|a| a == expected.as_bytes()),
"entry ref for {path} not found in addresses"
);
}
}
#[test]
fn partial_update_workflow() {
let store = Store::new();
let mut m = PlainManifest::new(store);
for i in 0..100u32 {
let path = format!("dir{}/file{}.txt", i / 10, i);
let addr = make_addr_u32(i);
m.add(&path, addr).unwrap();
}
let root_ref_1 = m.save().unwrap();
let updated_addr = make_addr_u32(999);
m.add("dir0/file0.txt", updated_addr).unwrap();
let root_ref_2 = m.save().unwrap();
assert_ne!(root_ref_1, root_ref_2);
let entry = m.lookup("dir0/file0.txt").unwrap();
assert_eq!(entry.address(), Some(&updated_addr));
for i in 1..100u32 {
let path = format!("dir{}/file{}.txt", i / 10, i);
let entry = m.lookup(&path).unwrap();
let expected = make_addr_u32(i);
assert_eq!(
entry.address(),
Some(&expected),
"entry at {path} was corrupted"
);
}
}
#[test]
fn into_iterator() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["index.html", "img/1.png", "img/2.png", "robots.txt"];
for &path in paths {
let addr = make_addr(path);
m.add(path, addr).unwrap();
}
let mut all_entries = Vec::new();
for result in &mut m {
all_entries.push(result.unwrap());
}
assert_eq!(all_entries.len(), paths.len());
for &path in paths {
assert!(
all_entries.iter().any(|e| e.path() == path.as_bytes()),
"path {path} not found via IntoIterator"
);
}
}
#[test]
fn manifest_iter_lazy() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["index.html", "img/1.png", "img/2.png", "robots.txt"];
for &path in paths {
let addr = make_addr(path);
m.add(path, addr).unwrap();
}
let root_ref = m.save().unwrap();
let (_, store) = m.into_parts();
let mut m2 = PlainManifest::open(root_ref, store);
let mut visited = Vec::new();
if let Some(result) = m2.iter().next() {
let entry = result.unwrap();
visited.push(entry.path);
}
assert_eq!(visited.len(), 1);
let (_, store) = m2.into_parts();
let mut m3 = PlainManifest::open(root_ref, store);
let mut all_entries = Vec::new();
for result in m3.iter() {
all_entries.push(result.unwrap());
}
assert_eq!(all_entries.len(), paths.len());
for &path in paths {
assert!(
all_entries.iter().any(|e| e.path() == path.as_bytes()),
"path {path} not found via iterator"
);
}
}
#[test]
fn iter_empty_manifest() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let entries: Vec<_> = m.iter().collect();
assert!(entries.is_empty(), "empty manifest should yield no entries");
}
#[test]
fn iter_single_entry() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let addr = make_addr("only");
m.add("only.txt", addr).unwrap();
let entries: Vec<_> = m.iter().map(|r| r.unwrap()).collect();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].path(), b"only.txt");
assert_eq!(entries[0].address(), Some(&addr));
}
#[test]
fn iter_deep_trie_with_lazy_loading() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let deep_paths: Vec<String> = (0..20)
.map(|i| format!("a/b/c/d/e/f/g/h/file{i:02}.dat"))
.collect();
for path in &deep_paths {
m.add(path, make_addr(path)).unwrap();
}
let root_ref = m.save().unwrap();
let (_, store) = m.into_parts();
let mut m2 = PlainManifest::open(root_ref, store);
let entries: Vec<_> = m2.iter().map(|r| r.unwrap()).collect();
assert_eq!(entries.len(), deep_paths.len());
for path in &deep_paths {
assert!(
entries.iter().any(|e| e.path() == path.as_bytes()),
"deep path {path} not found via iterator"
);
}
}
#[test]
fn iter_partial_then_reiterate() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["a.txt", "b.txt", "c.txt", "d.txt", "e.txt"];
for &path in paths {
m.add(path, make_addr(path)).unwrap();
}
{
let mut iter = m.iter();
let _first = iter.next().unwrap().unwrap();
let _second = iter.next().unwrap().unwrap();
}
let all: Vec<_> = m.iter().map(|r| r.unwrap()).collect();
assert_eq!(all.len(), paths.len());
for &path in paths {
assert!(
all.iter().any(|e| e.path() == path.as_bytes()),
"path {path} missing after partial iteration + re-iteration"
);
}
}
#[test]
fn iter_partial_then_reiterate_lazy() {
let store = Store::new();
let mut m = PlainManifest::new(store);
let paths = &["x/1.txt", "x/2.txt", "y/1.txt", "y/2.txt", "z.txt"];
for &path in paths {
m.add(path, make_addr(path)).unwrap();
}
let root_ref = m.save().unwrap();
let (_, store) = m.into_parts();
let mut m2 = PlainManifest::open(root_ref, store);
{
let mut iter = m2.iter();
let _first = iter.next().unwrap().unwrap();
}
let all: Vec<_> = m2.iter().map(|r| r.unwrap()).collect();
assert_eq!(all.len(), paths.len());
for &path in paths {
assert!(
all.iter().any(|e| e.path() == path.as_bytes()),
"path {path} missing after partial lazy iteration"
);
}
}
}