use serde::{Deserialize, Serialize};
pub const EXTERNALIZE_THRESHOLD: usize = 64 * 1024;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExternRef {
pub hash: [u8; 32],
pub len: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValuePlacement {
Inline(Vec<u8>),
Externalized(ExternRef),
}
pub fn content_hash(bytes: &[u8]) -> [u8; 32] {
*blake3::hash(bytes).as_bytes()
}
pub fn should_externalize(len: usize) -> bool {
len > EXTERNALIZE_THRESHOLD
}
pub fn classify(bytes: &[u8]) -> ValuePlacement {
if should_externalize(bytes.len()) {
ValuePlacement::Externalized(ExternRef {
hash: content_hash(bytes),
len: bytes.len() as u64,
})
} else {
ValuePlacement::Inline(bytes.to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{decode_value, encode_value};
#[test]
fn small_values_stay_inline() {
let small = vec![7u8; EXTERNALIZE_THRESHOLD]; assert!(matches!(classify(&small), ValuePlacement::Inline(_)));
assert!(!should_externalize(small.len()));
}
#[test]
fn large_values_are_externalized_with_hash_and_len() {
let big = vec![7u8; EXTERNALIZE_THRESHOLD + 1];
match classify(&big) {
ValuePlacement::Externalized(r) => {
assert_eq!(r.len, big.len() as u64);
assert_eq!(r.hash, content_hash(&big));
}
ValuePlacement::Inline(_) => panic!("expected externalization just over the threshold"),
}
}
#[test]
fn content_hash_is_deterministic_and_content_sensitive() {
let a = content_hash(b"hello");
assert_eq!(a, content_hash(b"hello"));
assert_ne!(a, content_hash(b"hellp"));
}
#[test]
fn extern_ref_round_trips_through_the_value_codec() {
let r = ExternRef {
hash: content_hash(b"payload"),
len: 123,
};
let bytes = encode_value(&r).unwrap();
assert_eq!(decode_value::<ExternRef>(&bytes).unwrap(), r);
}
}