use std::collections::HashMap;
pub fn fnv1a_64(data: &[u8]) -> u64 {
const OFFSET: u64 = 14_695_981_039_346_656_037;
const PRIME: u64 = 1_099_511_628_211;
let mut hash = OFFSET;
for &b in data {
hash ^= b as u64;
hash = hash.wrapping_mul(PRIME);
}
hash
}
pub fn djb2(data: &[u8]) -> u32 {
let mut hash: u64 = 5381;
for &b in data {
hash = hash.wrapping_mul(33).wrapping_add(b as u64);
}
(hash & 0xFFFF_FFFF) as u32
}
pub fn murmur3_32(data: &[u8]) -> u32 {
const C1: u32 = 0xcc9e_2d51;
const C2: u32 = 0x1b87_3593;
let len = data.len();
let mut h: u32 = 0;
let nblocks = len / 4;
for i in 0..nblocks {
let idx = i * 4;
let k = u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
let k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
h ^= k;
h = h.rotate_left(13);
h = h.wrapping_mul(5).wrapping_add(0xe654_6b64);
}
let tail_start = nblocks * 4;
let tail = &data[tail_start..];
let mut k: u32 = 0;
match tail.len() {
3 => {
k ^= (tail[2] as u32) << 16;
k ^= (tail[1] as u32) << 8;
k ^= tail[0] as u32;
k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
h ^= k;
}
2 => {
k ^= (tail[1] as u32) << 8;
k ^= tail[0] as u32;
k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
h ^= k;
}
1 => {
k ^= tail[0] as u32;
k = k.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
h ^= k;
}
_ => {}
}
h ^= len as u32;
h ^= h >> 16;
h = h.wrapping_mul(0x85eb_ca6b);
h ^= h >> 13;
h = h.wrapping_mul(0xc2b2_ae35);
h ^= h >> 16;
h
}
pub fn adler32(data: &[u8]) -> u32 {
const MOD: u32 = 65521;
let mut s1: u32 = 1;
let mut s2: u32 = 0;
for &b in data {
s1 = (s1 + b as u32) % MOD;
s2 = (s2 + s1) % MOD;
}
(s2 << 16) | s1
}
fn crc32_table() -> [u32; 256] {
let mut table = [0u32; 256];
for i in 0u32..256 {
let mut crc = i;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB8_8320;
} else {
crc >>= 1;
}
}
table[i as usize] = crc;
}
table
}
pub fn crc32_iso(data: &[u8]) -> u32 {
let table = crc32_table();
let mut crc: u32 = 0xFFFF_FFFF;
for &b in data {
let idx = ((crc ^ b as u32) & 0xFF) as usize;
crc = (crc >> 8) ^ table[idx];
}
crc ^ 0xFFFF_FFFF
}
pub fn xxhash64_simple(data: &[u8]) -> u64 {
const PRIME1: u64 = 11_400_714_785_074_694_791;
const PRIME2: u64 = 14_029_467_366_897_019_727;
const PRIME3: u64 = 1_609_587_929_392_839_161;
const PRIME4: u64 = 9_650_029_242_287_828_579;
const PRIME5: u64 = 2_870_177_450_012_600_261;
let len = data.len();
let mut pos = 0usize;
let mut h64: u64;
if len >= 32 {
let mut v1 = 0u64.wrapping_add(PRIME1).wrapping_add(PRIME2);
let mut v2 = 0u64.wrapping_add(PRIME2);
let mut v3 = 0u64;
let mut v4 = 0u64.wrapping_sub(PRIME1);
while pos + 32 <= len {
let lane = |off: usize| {
u64::from_le_bytes([
data[pos + off],
data[pos + off + 1],
data[pos + off + 2],
data[pos + off + 3],
data[pos + off + 4],
data[pos + off + 5],
data[pos + off + 6],
data[pos + off + 7],
])
};
v1 = v1
.wrapping_add(lane(0).wrapping_mul(PRIME2))
.rotate_left(31)
.wrapping_mul(PRIME1);
v2 = v2
.wrapping_add(lane(8).wrapping_mul(PRIME2))
.rotate_left(31)
.wrapping_mul(PRIME1);
v3 = v3
.wrapping_add(lane(16).wrapping_mul(PRIME2))
.rotate_left(31)
.wrapping_mul(PRIME1);
v4 = v4
.wrapping_add(lane(24).wrapping_mul(PRIME2))
.rotate_left(31)
.wrapping_mul(PRIME1);
pos += 32;
}
h64 = v1
.rotate_left(1)
.wrapping_add(v2.rotate_left(7))
.wrapping_add(v3.rotate_left(12))
.wrapping_add(v4.rotate_left(18));
let merge = |acc: u64, v: u64| -> u64 {
let v = v.wrapping_mul(PRIME2).rotate_left(31).wrapping_mul(PRIME1);
acc.wrapping_mul(PRIME1)
.wrapping_add(v)
.wrapping_mul(PRIME4)
.wrapping_add(PRIME3)
};
h64 = merge(h64, v1);
h64 = merge(h64, v2);
h64 = merge(h64, v3);
h64 = merge(h64, v4);
} else {
h64 = 0u64.wrapping_add(PRIME5);
}
h64 = h64.wrapping_add(len as u64);
while pos + 8 <= len {
let lane = u64::from_le_bytes([
data[pos],
data[pos + 1],
data[pos + 2],
data[pos + 3],
data[pos + 4],
data[pos + 5],
data[pos + 6],
data[pos + 7],
]);
let k1 = lane
.wrapping_mul(PRIME2)
.rotate_left(31)
.wrapping_mul(PRIME1);
h64 ^= k1;
h64 = h64
.rotate_left(27)
.wrapping_mul(PRIME1)
.wrapping_add(PRIME4);
pos += 8;
}
if pos + 4 <= len {
let lane = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
h64 ^= (lane as u64).wrapping_mul(PRIME1);
h64 = h64
.rotate_left(23)
.wrapping_mul(PRIME2)
.wrapping_add(PRIME3);
pos += 4;
}
while pos < len {
h64 ^= (data[pos] as u64).wrapping_mul(PRIME5);
h64 = h64.rotate_left(11).wrapping_mul(PRIME1);
pos += 1;
}
h64 ^= h64 >> 33;
h64 = h64.wrapping_mul(PRIME2);
h64 ^= h64 >> 29;
h64 = h64.wrapping_mul(PRIME1);
h64 ^= h64 >> 32;
h64
}
pub fn blake3_256_simple(data: &[u8]) -> [u8; 32] {
const SEEDS: [u64; 4] = [
0x0000_0000_0000_0000,
0x1234_5678_90AB_CDEF,
0xFEDC_BA98_7654_3210,
0xDEAD_BEEF_CAFE_BABE,
];
const PRIME: u64 = 1_099_511_628_211;
const OFFSET: u64 = 14_695_981_039_346_656_037;
let mut result = [0u8; 32];
for (i, &seed) in SEEDS.iter().enumerate() {
let mut hash = OFFSET ^ seed;
for &b in data {
hash ^= b as u64;
hash = hash.wrapping_mul(PRIME);
}
let bytes = hash.to_le_bytes();
result[i * 8..(i + 1) * 8].copy_from_slice(&bytes);
}
result
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChecksumAlgorithm {
Fnv1a64,
Djb2,
Murmur3_32,
Adler32,
Crc32,
Xxhash64,
Blake3_256,
}
impl ChecksumAlgorithm {
pub fn name(&self) -> &'static str {
match self {
Self::Fnv1a64 => "fnv1a64",
Self::Djb2 => "djb2",
Self::Murmur3_32 => "murmur3_32",
Self::Adler32 => "adler32",
Self::Crc32 => "crc32",
Self::Xxhash64 => "xxhash64",
Self::Blake3_256 => "blake3_256",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checksum {
pub algorithm: ChecksumAlgorithm,
pub value: Vec<u8>,
pub hex: String,
}
impl Checksum {
pub fn new(algorithm: ChecksumAlgorithm, value: Vec<u8>) -> Self {
let hex = value.iter().map(|b| format!("{b:02x}")).collect();
Self {
algorithm,
value,
hex,
}
}
}
#[derive(Debug, Clone)]
pub struct ChecksumRecord {
pub object_id: String,
pub checksum: Checksum,
pub computed_at: u64,
pub verified_at: Option<u64>,
pub size_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct CeVerificationResult {
pub object_id: String,
pub expected: Checksum,
pub actual: Checksum,
pub matches: bool,
pub verified_at: u64,
}
#[derive(Debug, Clone)]
pub struct ChecksumStats {
pub total_records: usize,
pub verified_count: usize,
pub corruption_rate: f64,
pub algorithm: String,
}
#[derive(Debug, Clone)]
pub struct StorageChecksumEngine {
records: HashMap<String, ChecksumRecord>,
pub algorithm: ChecksumAlgorithm,
pub verify_on_read: bool,
}
impl StorageChecksumEngine {
pub fn new(algorithm: ChecksumAlgorithm) -> Self {
Self {
records: HashMap::new(),
algorithm,
verify_on_read: false,
}
}
pub fn compute(&self, data: &[u8]) -> Checksum {
Self::compute_with_algorithm(self.algorithm, data)
}
pub fn compute_with_algorithm(algorithm: ChecksumAlgorithm, data: &[u8]) -> Checksum {
let value: Vec<u8> = match algorithm {
ChecksumAlgorithm::Fnv1a64 => fnv1a_64(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Djb2 => djb2(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Murmur3_32 => murmur3_32(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Adler32 => adler32(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Crc32 => crc32_iso(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Xxhash64 => xxhash64_simple(data).to_le_bytes().to_vec(),
ChecksumAlgorithm::Blake3_256 => blake3_256_simple(data).to_vec(),
};
Checksum::new(algorithm, value)
}
pub fn compute_for(&mut self, object_id: String, data: &[u8], now: u64) -> ChecksumRecord {
let checksum = self.compute(data);
let record = ChecksumRecord {
object_id: object_id.clone(),
checksum,
computed_at: now,
verified_at: None,
size_bytes: data.len() as u64,
};
self.records.insert(object_id, record.clone());
record
}
pub fn verify(
&mut self,
object_id: &str,
data: &[u8],
now: u64,
) -> Option<CeVerificationResult> {
let record = self.records.get_mut(object_id)?;
let actual = Self::compute_with_algorithm(record.checksum.algorithm, data);
let matches = actual.value == record.checksum.value;
if matches {
record.verified_at = Some(now);
}
Some(CeVerificationResult {
object_id: object_id.to_owned(),
expected: record.checksum.clone(),
actual,
matches,
verified_at: now,
})
}
pub fn record(&self, object_id: &str) -> Option<&ChecksumRecord> {
self.records.get(object_id)
}
pub fn remove(&mut self, object_id: &str) -> bool {
self.records.remove(object_id).is_some()
}
pub fn verify_all(
&mut self,
data_fn: impl Fn(&str) -> Option<Vec<u8>>,
now: u64,
) -> Vec<CeVerificationResult> {
let ids: Vec<String> = self.records.keys().cloned().collect();
let mut results = Vec::with_capacity(ids.len());
for id in ids {
if let Some(data) = data_fn(&id) {
if let Some(result) = self.verify(&id, &data, now) {
results.push(result);
}
}
}
results
}
pub fn batch_compute(&mut self, items: &[(&str, &[u8])], now: u64) -> Vec<ChecksumRecord> {
items
.iter()
.map(|&(id, data)| self.compute_for(id.to_owned(), data, now))
.collect()
}
pub fn corruption_count(results: &[CeVerificationResult]) -> usize {
results.iter().filter(|r| !r.matches).count()
}
pub fn object_count(&self) -> usize {
self.records.len()
}
pub fn stats(&self, results: &[CeVerificationResult]) -> ChecksumStats {
let total_records = self.records.len();
let verified_count = self
.records
.values()
.filter(|r| r.verified_at.is_some())
.count();
let corrupted = Self::corruption_count(results);
let corruption_rate = if results.is_empty() {
0.0
} else {
corrupted as f64 / results.len() as f64
};
ChecksumStats {
total_records,
verified_count,
corruption_rate,
algorithm: self.algorithm.name().to_owned(),
}
}
}
#[cfg(test)]
mod tests {
use crate::checksum_engine::{
adler32, blake3_256_simple, crc32_iso, djb2, fnv1a_64, murmur3_32, xxhash64_simple,
CeVerificationResult, ChecksumAlgorithm, ChecksumRecord, StorageChecksumEngine,
};
#[test]
fn test_fnv1a64_empty() {
assert_eq!(fnv1a_64(b""), 14_695_981_039_346_656_037);
}
#[test]
fn test_fnv1a64_known_value() {
let h = fnv1a_64(b"hello");
assert_ne!(h, 0);
assert_eq!(fnv1a_64(b"hello"), h);
}
#[test]
fn test_fnv1a64_different_inputs_differ() {
assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
}
#[test]
fn test_djb2_empty() {
let h = djb2(b"");
assert_eq!(h, (5381u64 & 0xFFFF_FFFF) as u32);
}
#[test]
fn test_djb2_hello() {
let h = djb2(b"hello");
assert_ne!(h, 0);
assert_eq!(djb2(b"hello"), h);
}
#[test]
fn test_djb2_different() {
assert_ne!(djb2(b"abc"), djb2(b"xyz"));
}
#[test]
fn test_murmur3_32_empty() {
let h = murmur3_32(b"");
assert_eq!(h, murmur3_32(b""));
}
#[test]
fn test_murmur3_32_4bytes() {
let h = murmur3_32(b"test");
assert_ne!(h, 0);
assert_eq!(murmur3_32(b"test"), h);
}
#[test]
fn test_murmur3_32_partial_tail() {
let h = murmur3_32(b"abcde");
assert_eq!(murmur3_32(b"abcde"), h);
}
#[test]
fn test_adler32_empty() {
assert_eq!(adler32(b""), 1);
}
#[test]
fn test_adler32_abc() {
assert_eq!(adler32(b"abc"), 0x024d_0127);
}
#[test]
fn test_adler32_deterministic() {
let h = adler32(b"hello world");
assert_eq!(adler32(b"hello world"), h);
}
#[test]
fn test_crc32_empty() {
assert_eq!(crc32_iso(b""), 0x0000_0000);
}
#[test]
fn test_crc32_known_value() {
assert_eq!(crc32_iso(b"123456789"), 0xCBF4_3926);
}
#[test]
fn test_crc32_deterministic() {
assert_eq!(crc32_iso(b"hello"), crc32_iso(b"hello"));
}
#[test]
fn test_xxhash64_empty() {
let h = xxhash64_simple(b"");
assert_eq!(xxhash64_simple(b""), h);
}
#[test]
fn test_xxhash64_hello() {
let h = xxhash64_simple(b"hello");
assert_ne!(h, 0);
assert_eq!(xxhash64_simple(b"hello"), h);
}
#[test]
fn test_xxhash64_large_input() {
let data: Vec<u8> = (0u8..64).collect();
let h = xxhash64_simple(&data);
assert_eq!(xxhash64_simple(&data), h);
}
#[test]
fn test_blake3_256_empty() {
let h = blake3_256_simple(b"");
assert_eq!(h.len(), 32);
assert_eq!(blake3_256_simple(b""), h);
}
#[test]
fn test_blake3_256_hello() {
let h = blake3_256_simple(b"hello");
assert_eq!(h.len(), 32);
assert_ne!(h, [0u8; 32]);
}
#[test]
fn test_blake3_256_different_inputs() {
assert_ne!(blake3_256_simple(b"a"), blake3_256_simple(b"b"));
}
#[test]
fn test_checksum_hex_encoding() {
let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
let cs = engine.compute(b"hello");
assert_eq!(cs.hex.len(), 16); assert!(cs.hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_checksum_hex_lowercase() {
let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
let cs = engine.compute(b"123456789");
assert!(cs.hex.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn test_checksum_blake3_hex_length() {
let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Blake3_256);
let cs = engine.compute(b"data");
assert_eq!(cs.hex.len(), 64); }
#[test]
fn test_compute_for_stores_record() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
let rec = engine.compute_for("obj1".to_owned(), b"data", 1000);
assert_eq!(rec.object_id, "obj1");
assert_eq!(rec.computed_at, 1000);
assert_eq!(rec.size_bytes, 4);
assert!(rec.verified_at.is_none());
assert_eq!(engine.object_count(), 1);
}
#[test]
fn test_record_retrieval() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Adler32);
engine.compute_for("x".to_owned(), b"hello", 42);
let rec = engine.record("x");
assert!(rec.is_some());
assert_eq!(rec.map(|r| r.object_id.as_str()), Some("x"));
}
#[test]
fn test_record_missing_returns_none() {
let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Djb2);
assert!(engine.record("nonexistent").is_none());
}
#[test]
fn test_remove_existing() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
engine.compute_for("a".to_owned(), b"x", 0);
assert!(engine.remove("a"));
assert_eq!(engine.object_count(), 0);
}
#[test]
fn test_remove_nonexistent() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
assert!(!engine.remove("ghost"));
}
#[test]
fn test_verify_matching() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("doc".to_owned(), b"content", 100);
let result = engine
.verify("doc", b"content", 200)
.expect("result expected");
assert!(result.matches);
assert_eq!(result.verified_at, 200);
assert_eq!(engine.record("doc").and_then(|r| r.verified_at), Some(200));
}
#[test]
fn test_verify_mismatch() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("doc".to_owned(), b"original", 100);
let result = engine.verify("doc", b"corrupted", 200).expect("result");
assert!(!result.matches);
assert!(engine.record("doc").and_then(|r| r.verified_at).is_none());
}
#[test]
fn test_verify_missing_object() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
assert!(engine.verify("missing", b"data", 0).is_none());
}
#[test]
fn test_verify_updates_verified_at() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Xxhash64);
engine.compute_for("item".to_owned(), b"payload", 50);
let r = engine.verify("item", b"payload", 999).expect("ok");
assert!(r.matches);
assert_eq!(engine.record("item").and_then(|r| r.verified_at), Some(999));
}
#[test]
fn test_batch_compute() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Murmur3_32);
let items: Vec<(&str, &[u8])> = vec![("a", b"alpha"), ("b", b"beta"), ("c", b"gamma")];
let records: Vec<ChecksumRecord> = engine.batch_compute(&items, 777);
assert_eq!(records.len(), 3);
assert_eq!(engine.object_count(), 3);
}
#[test]
fn test_batch_compute_all_stored() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Adler32);
let items: Vec<(&str, &[u8])> = vec![("x", b"1"), ("y", b"2")];
engine.batch_compute(&items, 10);
assert!(engine.record("x").is_some());
assert!(engine.record("y").is_some());
}
#[test]
fn test_verify_all_clean() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("p".to_owned(), b"ping", 1);
engine.compute_for("q".to_owned(), b"pong", 1);
let results = engine.verify_all(
|id| {
if id == "p" {
Some(b"ping".to_vec())
} else {
Some(b"pong".to_vec())
}
},
2,
);
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.matches));
}
#[test]
fn test_verify_all_with_corruption() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("good".to_owned(), b"ok", 1);
engine.compute_for("bad".to_owned(), b"original", 1);
let results = engine.verify_all(
|id| {
if id == "good" {
Some(b"ok".to_vec())
} else {
Some(b"corrupted".to_vec())
}
},
2,
);
let corrupt = StorageChecksumEngine::corruption_count(&results);
assert_eq!(corrupt, 1);
}
#[test]
fn test_verify_all_data_fn_returns_none() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
engine.compute_for("present".to_owned(), b"data", 0);
engine.compute_for("absent".to_owned(), b"x", 0);
let results = engine.verify_all(
|id| {
if id == "present" {
Some(b"data".to_vec())
} else {
None
}
},
5,
);
assert_eq!(results.len(), 1);
}
#[test]
fn test_corruption_count_zero() {
let results: Vec<CeVerificationResult> = vec![];
assert_eq!(StorageChecksumEngine::corruption_count(&results), 0);
}
#[test]
fn test_stats_empty() {
let engine = StorageChecksumEngine::new(ChecksumAlgorithm::Blake3_256);
let stats = engine.stats(&[]);
assert_eq!(stats.total_records, 0);
assert_eq!(stats.verified_count, 0);
assert_eq!(stats.corruption_rate, 0.0);
assert_eq!(stats.algorithm, "blake3_256");
}
#[test]
fn test_stats_after_verification() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("a".to_owned(), b"hello", 1);
engine.compute_for("b".to_owned(), b"world", 1);
let results = engine.verify_all(
|id| {
if id == "a" {
Some(b"hello".to_vec())
} else {
Some(b"corrupted".to_vec())
}
},
2,
);
let stats = engine.stats(&results);
assert_eq!(stats.total_records, 2);
assert_eq!(stats.verified_count, 1); assert!((stats.corruption_rate - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_algorithm_name() {
assert_eq!(ChecksumAlgorithm::Fnv1a64.name(), "fnv1a64");
assert_eq!(ChecksumAlgorithm::Djb2.name(), "djb2");
assert_eq!(ChecksumAlgorithm::Blake3_256.name(), "blake3_256");
assert_eq!(ChecksumAlgorithm::Crc32.name(), "crc32");
}
#[test]
fn test_all_algorithms_produce_nonzero_for_nonempty() {
let data = b"checksum test data";
let algos = [
ChecksumAlgorithm::Fnv1a64,
ChecksumAlgorithm::Djb2,
ChecksumAlgorithm::Murmur3_32,
ChecksumAlgorithm::Adler32,
ChecksumAlgorithm::Crc32,
ChecksumAlgorithm::Xxhash64,
ChecksumAlgorithm::Blake3_256,
];
for algo in algos {
let cs = StorageChecksumEngine::compute_with_algorithm(algo, data);
assert!(
cs.value.iter().any(|&b| b != 0),
"Algorithm {:?} returned all-zero for non-empty input",
algo
);
}
}
#[test]
fn test_overwrite_record_on_recompute() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Crc32);
engine.compute_for("obj".to_owned(), b"v1", 10);
engine.compute_for("obj".to_owned(), b"v2", 20);
assert_eq!(engine.object_count(), 1);
let rec = engine.record("obj").expect("present");
assert_eq!(rec.computed_at, 20);
assert_eq!(rec.size_bytes, 2);
}
#[test]
fn test_verify_on_read_flag() {
let mut engine = StorageChecksumEngine::new(ChecksumAlgorithm::Fnv1a64);
engine.verify_on_read = true;
assert!(engine.verify_on_read);
}
#[test]
fn test_compute_with_algorithm_static() {
let cs = StorageChecksumEngine::compute_with_algorithm(ChecksumAlgorithm::Adler32, b"abc");
assert_eq!(cs.algorithm, ChecksumAlgorithm::Adler32);
let expected = adler32(b"abc").to_le_bytes().to_vec();
assert_eq!(cs.value, expected);
}
}