#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkingProfile {
pub profile_version: u32,
pub min_size: usize,
pub mask_bits: u32,
pub max_size: usize,
pub gear_seed: u64,
}
pub const PROFILE_V1: ChunkingProfile = ChunkingProfile {
profile_version: 1,
min_size: 16 * 1024,
mask_bits: 16,
max_size: 256 * 1024,
gear_seed: 0x9e37_79b9_7f4a_7c15,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkSpan {
pub offset: usize,
pub length: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChunkManifest {
pub profile_version: u32,
pub spans: Vec<ChunkSpan>,
}
fn gear_table(seed: u64) -> [u64; 256] {
let mut table = [0u64; 256];
let mut state = seed;
for entry in &mut table {
state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
*entry = z ^ (z >> 31);
}
table
}
#[must_use]
pub fn chunk(data: &[u8], profile: &ChunkingProfile) -> ChunkManifest {
let table = gear_table(profile.gear_seed);
let mask: u64 = (1u64 << profile.mask_bits) - 1;
let mut spans = Vec::new();
let mut start = 0usize;
while start < data.len() {
let remaining = data.len() - start;
if remaining <= profile.min_size {
spans.push(ChunkSpan {
offset: start,
length: remaining,
});
break;
}
let mut hash: u64 = 0;
let mut cut = remaining.min(profile.max_size);
let window_end = remaining.min(profile.max_size);
for (i, byte) in data[start..start + window_end].iter().enumerate() {
hash = (hash << 1).wrapping_add(table[*byte as usize]);
if i >= profile.min_size && (hash & mask) == 0 {
cut = i + 1;
break;
}
}
spans.push(ChunkSpan {
offset: start,
length: cut,
});
start += cut;
}
ChunkManifest {
profile_version: profile.profile_version,
spans,
}
}
pub fn validate_coverage(manifest: &ChunkManifest, object_len: usize) -> Result<(), &'static str> {
let mut expected = 0usize;
for span in &manifest.spans {
if span.offset != expected {
return Err("spans must tile the object contiguously");
}
if span.length == 0 {
return Err("zero-length span");
}
expected += span.length;
}
if expected != object_len {
return Err("spans do not cover the object length");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes(len: usize, seed: u64) -> Vec<u8> {
let mut state = seed;
(0..len)
.map(|_| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(state >> 56) as u8
})
.collect()
}
#[test]
fn chunking_is_deterministic_and_bounded() {
let data = bytes(2 * 1024 * 1024, 7);
let a = chunk(&data, &PROFILE_V1);
let b = chunk(&data, &PROFILE_V1);
assert_eq!(a, b, "same bytes + same profile = identical layout");
assert!(validate_coverage(&a, data.len()).is_ok());
for span in &a.spans[..a.spans.len() - 1] {
assert!(span.length >= PROFILE_V1.min_size, "min bound");
assert!(span.length <= PROFILE_V1.max_size, "max bound");
}
assert!(a.spans.len() > 4, "2 MiB must cut into several chunks");
}
#[test]
fn an_insertion_resynchronizes_locally() {
let original = bytes(1024 * 1024, 9);
let mut edited = original.clone();
edited.splice(10_000..10_000, bytes(64, 11));
let a = chunk(&original, &PROFILE_V1);
let b = chunk(&edited, &PROFILE_V1);
let content = |data: &[u8], m: &ChunkManifest| -> Vec<Vec<u8>> {
m.spans
.iter()
.map(|s| data[s.offset..s.offset + s.length].to_vec())
.collect()
};
let ca = content(&original, &a);
let cb = content(&edited, &b);
let shared = ca.iter().filter(|c| cb.contains(c)).count();
assert!(
shared * 2 > ca.len(),
"majority of chunks must survive a 64-byte insertion \
({shared}/{} shared)",
ca.len()
);
}
#[test]
fn parameter_version_bump_creates_new_manifests_without_invalidating_old() {
let data = bytes(512 * 1024, 3);
let old_manifest = chunk(&data, &PROFILE_V1);
let v2 = ChunkingProfile {
profile_version: 2,
mask_bits: 14, min_size: 4 * 1024,
..PROFILE_V1
};
let new_manifest = chunk(&data, &v2);
assert_ne!(old_manifest.spans, new_manifest.spans);
assert_eq!(old_manifest.profile_version, 1);
assert_eq!(new_manifest.profile_version, 2);
assert!(validate_coverage(&old_manifest, data.len()).is_ok());
assert!(validate_coverage(&new_manifest, data.len()).is_ok());
}
#[test]
fn degenerate_objects_chunk_sanely() {
let empty = chunk(&[], &PROFILE_V1);
assert!(empty.spans.is_empty());
assert!(validate_coverage(&empty, 0).is_ok());
let tiny = chunk(&[1, 2, 3], &PROFILE_V1);
assert_eq!(tiny.spans.len(), 1);
assert!(validate_coverage(&tiny, 3).is_ok());
let torn = ChunkManifest {
profile_version: 1,
spans: vec![ChunkSpan {
offset: 0,
length: 2,
}],
};
assert!(validate_coverage(&torn, 3).is_err());
}
}