#![allow(missing_docs)]
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
pub type Tree = BTreeMap<String, Vec<u8>>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Quad {
pub root: [u8; 32],
pub pointer: [u8; 32],
pub tree: Tree,
}
impl Quad {
pub fn new(root: [u8; 32], pointer: [u8; 32], tree: Tree) -> Self {
Self {
root,
pointer,
tree,
}
}
pub fn empty() -> Self {
Self {
root: [0u8; 32],
pointer: [0u8; 32],
tree: Tree::new(),
}
}
pub fn from_strings(root: &str, pointer: &str, tree: Tree) -> Self {
Self {
root: blake3::hash(root.as_bytes()).into(),
pointer: blake3::hash(pointer.as_bytes()).into(),
tree,
}
}
pub fn content_hash(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&self.root);
hasher.update(&self.pointer);
for (key, value) in &self.tree {
hasher.update(key.as_bytes());
hasher.update(&(value.len() as u64).to_le_bytes());
hasher.update(value);
}
*hasher.finalize().as_bytes()
}
pub fn is_empty(&self) -> bool {
self.root == [0u8; 32] && self.pointer == [0u8; 32] && self.tree.is_empty()
}
}
impl Default for Quad {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_quad_is_empty() {
let q = Quad::empty();
assert!(q.is_empty());
}
#[test]
fn from_strings_produces_nonzero_hashes() {
let q = Quad::from_strings("origin", "next", Tree::new());
assert_ne!(q.root, [0u8; 32]);
assert_ne!(q.pointer, [0u8; 32]);
}
#[test]
fn content_hash_is_deterministic() {
let mut tree = Tree::new();
tree.insert("a".into(), vec![1, 2, 3]);
tree.insert("b".into(), vec![4, 5]);
let q = Quad::from_strings("root", "ptr", tree.clone());
let q2 = Quad::from_strings("root", "ptr", tree);
assert_eq!(q.content_hash(), q2.content_hash());
}
#[test]
fn content_hash_changes_on_any_modification() {
let mut tree = Tree::new();
tree.insert("key".into(), vec![1, 2, 3]);
let q1 = Quad::from_strings("root", "ptr", tree.clone());
let q2 = Quad::from_strings("root2", "ptr", tree.clone());
assert_ne!(q1.content_hash(), q2.content_hash());
let q3 = Quad::from_strings("root", "ptr2", tree.clone());
assert_ne!(q1.content_hash(), q3.content_hash());
let mut tree2 = tree.clone();
tree2.insert("key".into(), vec![1, 2, 4]);
let q4 = Quad::from_strings("root", "ptr", tree2);
assert_ne!(q1.content_hash(), q4.content_hash());
let mut tree3 = tree;
tree3.insert("key2".into(), vec![9]);
let q5 = Quad::from_strings("root", "ptr", tree3);
assert_ne!(q1.content_hash(), q5.content_hash());
}
#[test]
fn quad_does_not_have_external_key_field() {
let q = Quad::empty();
let _ = q.root;
let _ = q.pointer;
let _ = q.tree;
}
}