use base64::prelude::*;
use blake2::digest::typenum;
use blake2::{Blake2b, Digest};
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Fingerprint(pub [u8; 16]);
impl Fingerprint {
pub fn new(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn from_bytes(data: &[u8]) -> Self {
let mut hasher = Blake2b::<typenum::U16>::default();
hasher.update(data);
Self(hasher.finalize().into())
}
pub fn from_str(s: &str) -> Self {
Self::from_bytes(s.as_bytes())
}
pub fn to_base64(self) -> String {
BASE64_STANDARD.encode(self.0)
}
pub fn from_base64(s: &str) -> Result<Self, FingerprintError> {
let bytes = BASE64_STANDARD
.decode(s)
.map_err(|e| FingerprintError::InvalidBase64(e.to_string()))?;
let bytes: [u8; 16] = bytes
.try_into()
.map_err(|e: Vec<u8>| FingerprintError::InvalidLength(e.len()))?;
Ok(Self(bytes))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn is_zero(&self) -> bool {
self.0 == [0u8; 16]
}
pub fn zero() -> Self {
Self([0u8; 16])
}
}
impl std::fmt::Display for Fingerprint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for byte in &self.0 {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
impl std::fmt::Debug for Fingerprint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Fingerprint({})", self)
}
}
impl Hash for Fingerprint {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.0[..8]);
}
}
impl Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_base64())
}
}
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_base64(&s).map_err(serde::de::Error::custom)
}
}
impl Default for Fingerprint {
fn default() -> Self {
Self::zero()
}
}
#[derive(Debug, thiserror::Error)]
pub enum FingerprintError {
#[error("Invalid base64: {0}")]
InvalidBase64(String),
#[error("Invalid fingerprint length: {0}")]
InvalidLength(usize),
#[error("Serialization error: {0}")]
Serialization(String),
}
#[derive(Clone)]
pub struct Fingerprinter {
hasher: Blake2b<typenum::U16>,
}
impl Fingerprinter {
pub fn new() -> Self {
Self {
hasher: Blake2b::<typenum::U16>::default(),
}
}
pub fn into_fingerprint(self) -> Fingerprint {
Fingerprint(self.hasher.finalize().into())
}
pub fn with_str(mut self, s: &str) -> Self {
self.write_str(s);
self
}
pub fn write_str(&mut self, s: &str) {
self.write_type_tag("s");
self.write_varlen_bytes(s.as_bytes());
}
pub fn with_bytes(mut self, bytes: &[u8]) -> Self {
self.write_bytes(bytes);
self
}
pub fn write_bytes(&mut self, bytes: &[u8]) {
self.write_type_tag("b");
self.write_varlen_bytes(bytes);
}
pub fn with_usize(mut self, n: usize) -> Self {
self.write_usize(n);
self
}
pub fn write_usize(&mut self, n: usize) {
self.write_type_tag("u");
self.hasher.update((n as u64).to_le_bytes());
}
pub fn with_u64(mut self, n: u64) -> Self {
self.write_u64(n);
self
}
pub fn write_u64(&mut self, n: u64) {
self.write_type_tag("u8");
self.hasher.update(n.to_le_bytes());
}
pub fn with_i64(mut self, n: i64) -> Self {
self.write_i64(n);
self
}
pub fn write_i64(&mut self, n: i64) {
self.write_type_tag("i8");
self.hasher.update(n.to_le_bytes());
}
pub fn with_bool(mut self, b: bool) -> Self {
self.write_bool(b);
self
}
pub fn write_bool(&mut self, b: bool) {
self.write_type_tag(if b { "t" } else { "f" });
}
pub fn with_option_str(mut self, opt: Option<&str>) -> Self {
self.write_option_str(opt);
self
}
pub fn write_option_str(&mut self, opt: Option<&str>) {
match opt {
Some(s) => {
self.write_type_tag("some");
self.write_str(s);
}
None => {
self.write_type_tag("none");
}
}
}
pub fn with_fingerprint(mut self, fp: &Fingerprint) -> Self {
self.write_fingerprint(fp);
self
}
pub fn write_fingerprint(&mut self, fp: &Fingerprint) {
self.write_type_tag("fp");
self.hasher.update(&fp.0);
}
pub fn write_raw(&mut self, bytes: &[u8]) {
self.hasher.update(bytes);
}
fn write_type_tag(&mut self, tag: &str) {
self.hasher.update(tag.as_bytes());
self.hasher.update(b";");
}
fn write_varlen_bytes(&mut self, bytes: &[u8]) {
self.hasher.update((bytes.len() as u32).to_le_bytes());
self.hasher.update(bytes);
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodeFingerprint {
pub content: Fingerprint,
pub subtree: Fingerprint,
}
impl NodeFingerprint {
pub fn new(content: Fingerprint, subtree: Fingerprint) -> Self {
Self { content, subtree }
}
pub fn leaf(content: Fingerprint) -> Self {
Self {
content,
subtree: content,
}
}
pub fn zero() -> Self {
Self {
content: Fingerprint::zero(),
subtree: Fingerprint::zero(),
}
}
pub fn is_zero(&self) -> bool {
self.content.is_zero() && self.subtree.is_zero()
}
pub fn content_changed(&self, other: &Self) -> bool {
self.content != other.content
}
pub fn subtree_changed(&self, other: &Self) -> bool {
self.subtree != other.subtree
}
pub fn only_descendants_changed(&self, other: &Self) -> bool {
self.content == other.content && self.subtree != other.subtree
}
}
impl Default for NodeFingerprint {
fn default() -> Self {
Self::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fingerprint_from_str() {
let fp1 = Fingerprint::from_str("hello");
let fp2 = Fingerprint::from_str("hello");
let fp3 = Fingerprint::from_str("world");
assert_eq!(fp1, fp2);
assert_ne!(fp1, fp3);
}
#[test]
fn test_fingerprint_base64_roundtrip() {
let fp = Fingerprint::from_str("test content");
let encoded = fp.to_base64();
let decoded = Fingerprint::from_base64(&encoded).unwrap();
assert_eq!(fp, decoded);
}
#[test]
fn test_fingerprinter_chaining() {
let fp1 = Fingerprinter::new()
.with_str("title")
.with_str("content")
.into_fingerprint();
let fp2 = Fingerprinter::new()
.with_str("title")
.with_str("content")
.into_fingerprint();
let fp3 = Fingerprinter::new()
.with_str("title")
.with_str("different")
.into_fingerprint();
assert_eq!(fp1, fp2);
assert_ne!(fp1, fp3);
}
#[test]
fn test_fingerprinter_types() {
let fp1 = Fingerprinter::new()
.with_str("test")
.with_usize(42)
.with_bool(true)
.into_fingerprint();
let fp2 = Fingerprinter::new()
.with_str("test")
.with_usize(42)
.with_bool(true)
.into_fingerprint();
let fp3 = Fingerprinter::new()
.with_str("test")
.with_usize(43) .with_bool(true)
.into_fingerprint();
assert_eq!(fp1, fp2);
assert_ne!(fp1, fp3);
}
#[test]
fn test_node_fingerprint() {
let content = Fingerprint::from_str("content");
let subtree = Fingerprint::from_str("subtree");
let fp = NodeFingerprint::new(content, subtree);
assert!(!fp.is_zero());
assert_eq!(fp.content, content);
assert_eq!(fp.subtree, subtree);
}
#[test]
fn test_node_fingerprint_change_detection() {
let old = NodeFingerprint::new(
Fingerprint::from_str("content"),
Fingerprint::from_str("subtree"),
);
let new1 = NodeFingerprint::new(
Fingerprint::from_str("content"),
Fingerprint::from_str("different"),
);
assert!(new1.only_descendants_changed(&old));
assert!(!new1.content_changed(&old));
assert!(new1.subtree_changed(&old));
let new2 = NodeFingerprint::new(
Fingerprint::from_str("different"),
Fingerprint::from_str("subtree"),
);
assert!(!new2.only_descendants_changed(&old));
assert!(new2.content_changed(&old));
}
#[test]
fn test_fingerprint_serialization() {
let fp = Fingerprint::from_str("test serialization");
let json = serde_json::to_string(&fp).unwrap();
let decoded: Fingerprint = serde_json::from_str(&json).unwrap();
assert_eq!(fp, decoded);
}
#[test]
fn test_node_fingerprint_serialization() {
let fp = NodeFingerprint::new(
Fingerprint::from_str("content"),
Fingerprint::from_str("subtree"),
);
let json = serde_json::to_string(&fp).unwrap();
let decoded: NodeFingerprint = serde_json::from_str(&json).unwrap();
assert_eq!(fp, decoded);
}
}