use std::collections::VecDeque;
use citadel_core::types::{PageId, PageType};
use citadel_core::{Result, MERKLE_HASH_SIZE};
pub type MerkleHash = [u8; MERKLE_HASH_SIZE];
#[derive(Debug, Clone)]
pub struct PageDigest {
pub page_id: PageId,
pub page_type: PageType,
pub merkle_hash: MerkleHash,
pub children: Vec<PageId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffEntry {
pub key: Vec<u8>,
pub value: Vec<u8>,
pub val_type: u8,
}
#[derive(Debug, Clone)]
pub struct DiffResult {
pub entries: Vec<DiffEntry>,
pub pages_compared: u64,
pub subtrees_skipped: u64,
}
impl DiffResult {
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
pub trait TreeReader {
fn root_info(&self) -> Result<(PageId, MerkleHash)>;
fn page_digest(&self, page_id: PageId) -> Result<PageDigest>;
fn leaf_entries(&self, page_id: PageId) -> Result<Vec<DiffEntry>>;
fn subtree_entries(&self, page_id: PageId) -> Result<Vec<DiffEntry>> {
let digest = self.page_digest(page_id)?;
match digest.page_type {
PageType::Leaf => self.leaf_entries(page_id),
PageType::Branch => {
let mut entries = Vec::new();
for child in &digest.children {
entries.extend(self.subtree_entries(*child)?);
}
Ok(entries)
}
_ => Ok(Vec::new()),
}
}
}
pub fn merkle_diff(source: &dyn TreeReader, target: &dyn TreeReader) -> Result<DiffResult> {
let (src_root, src_root_hash) = source.root_info()?;
let (tgt_root, tgt_root_hash) = target.root_info()?;
let mut result = DiffResult {
entries: Vec::new(),
pages_compared: 0,
subtrees_skipped: 0,
};
if src_root_hash == tgt_root_hash {
return Ok(result);
}
let mut queue: VecDeque<(PageId, PageId)> = VecDeque::new();
queue.push_back((src_root, tgt_root));
while let Some((src_pid, tgt_pid)) = queue.pop_front() {
let src_digest = source.page_digest(src_pid)?;
let tgt_digest = target.page_digest(tgt_pid)?;
result.pages_compared += 1;
if src_digest.merkle_hash == tgt_digest.merkle_hash {
result.subtrees_skipped += 1;
continue;
}
match (src_digest.page_type, tgt_digest.page_type) {
(PageType::Leaf, PageType::Leaf) => {
result.entries.extend(source.leaf_entries(src_pid)?);
}
(PageType::Branch, PageType::Branch)
if src_digest.children.len() == tgt_digest.children.len() =>
{
for (sc, tc) in src_digest.children.iter().zip(&tgt_digest.children) {
queue.push_back((*sc, *tc));
}
}
_ => {
result.entries.extend(source.subtree_entries(src_pid)?);
}
}
}
Ok(result)
}
#[cfg(test)]
#[path = "diff_tests.rs"]
mod tests;