use fastcdc::v2020::FastCDC;
pub(super) const SINGLE_CHUNK_FAST_PATH_MAX_BYTES: usize = 64 * 1024;
pub(super) const MAX_BINARY_CAS_CHUNK_BYTES: usize = 4096 * 1024;
pub(crate) const CHUNK_ANCHOR_BYTES: usize = 16 * 1024 * 1024;
pub(crate) const MEDIA_CHUNK_BYTES: usize = 1024 * 1024;
const MIN_BINARY_CAS_CHUNK_BYTES: usize = 256 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct BinaryCasChunking {
min_bytes: usize,
average_bytes: usize,
max_bytes: usize,
anchor_bytes: usize,
single_chunk_fast_path_max_bytes: usize,
}
impl BinaryCasChunking {
pub(super) const fn content_defined_v4() -> Self {
Self {
min_bytes: MIN_BINARY_CAS_CHUNK_BYTES,
average_bytes: MEDIA_CHUNK_BYTES,
max_bytes: MAX_BINARY_CAS_CHUNK_BYTES,
anchor_bytes: CHUNK_ANCHOR_BYTES,
single_chunk_fast_path_max_bytes: SINGLE_CHUNK_FAST_PATH_MAX_BYTES,
}
}
}
impl Default for BinaryCasChunking {
fn default() -> Self {
Self::content_defined_v4()
}
}
pub(crate) fn chunk_ranges(data: &[u8]) -> Vec<(usize, usize)> {
chunk_ranges_with_chunking(data, BinaryCasChunking::default())
}
pub(super) fn chunk_ranges_with_chunking(
data: &[u8],
chunking: BinaryCasChunking,
) -> Vec<(usize, usize)> {
if data.is_empty() {
return Vec::new();
}
if data.len() <= chunking.single_chunk_fast_path_max_bytes {
return vec![(0, data.len())];
}
let mut out = Vec::with_capacity(data.len() / chunking.average_bytes + 2);
let mut anchor = 0usize;
while anchor < data.len() {
let anchor_end = anchor.saturating_add(chunking.anchor_bytes).min(data.len());
for chunk in FastCDC::new(
&data[anchor..anchor_end],
chunking.min_bytes as u32,
chunking.average_bytes as u32,
chunking.max_bytes as u32,
) {
let start = anchor + chunk.offset;
out.push((start, start + chunk.length));
}
anchor = anchor_end;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn structured_bytes(len: usize, seed: u64) -> Vec<u8> {
let mut bytes = vec![0; len];
let mut state = seed ^ 0xd1b5_4a32_d192_ed03;
for chunk in bytes.chunks_mut(8) {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
chunk.copy_from_slice(&state.to_le_bytes()[..chunk.len()]);
}
bytes
}
#[test]
fn default_chunking_targets_the_media_average() {
let chunking = BinaryCasChunking::default();
assert_eq!(chunking.average_bytes, MEDIA_CHUNK_BYTES);
assert_eq!(chunking.anchor_bytes, CHUNK_ANCHOR_BYTES);
assert_eq!(chunking.single_chunk_fast_path_max_bytes, 64 * 1024);
}
#[test]
fn single_chunk_fast_path_applies_through_64kib() {
let at_boundary = vec![0; 64 * 1024];
assert_eq!(chunk_ranges(&at_boundary), vec![(0, at_boundary.len())]);
}
#[test]
fn ranges_tile_the_payload_without_gaps_or_overlap() {
let data = structured_bytes(37 * 1024 * 1024, 7);
let ranges = chunk_ranges(&data);
assert!(ranges.len() > 4);
let mut cursor = 0;
for (start, end) in &ranges {
assert_eq!(*start, cursor);
assert!(end > start);
assert!(end - start <= MAX_BINARY_CAS_CHUNK_BYTES);
cursor = *end;
}
assert_eq!(cursor, data.len());
}
#[test]
fn every_anchor_offset_starts_a_chunk() {
let data = structured_bytes(37 * 1024 * 1024, 11);
let starts = chunk_ranges(&data)
.into_iter()
.map(|(start, _)| start)
.collect::<Vec<_>>();
for anchor in (0..data.len()).step_by(CHUNK_ANCHOR_BYTES) {
assert!(
starts.contains(&anchor),
"anchor {anchor} must be a forced boundary"
);
}
}
#[test]
fn an_insert_near_the_start_keeps_the_later_boundaries() {
let base = structured_bytes(20 * 1024 * 1024, 3);
let mut edited = Vec::with_capacity(base.len() + 4096);
edited.extend_from_slice(&base[..4096]);
edited.extend_from_slice(&structured_bytes(4096, 99));
edited.extend_from_slice(&base[4096..]);
let base_chunks = chunk_ranges(&base)
.into_iter()
.map(|(start, end)| blake3::hash(&base[start..end]))
.collect::<std::collections::HashSet<_>>();
let shared = chunk_ranges(&edited)
.into_iter()
.filter(|(start, end)| base_chunks.contains(&blake3::hash(&edited[*start..*end])))
.map(|(start, end)| end - start)
.sum::<usize>();
assert!(
shared * 4 > edited.len() * 3,
"content-defined boundaries must resynchronise after an insert, shared {shared} of {}",
edited.len()
);
}
#[test]
fn part_sized_prefixes_chunk_the_same_way_as_the_whole_payload() {
let data = structured_bytes(35 * 1024 * 1024, 5);
let whole = chunk_ranges(&data);
let mut assembled = Vec::new();
let mut anchor = 0usize;
while anchor < data.len() {
let end = (anchor + CHUNK_ANCHOR_BYTES).min(data.len());
assembled.extend(
chunk_ranges(&data[anchor..end])
.into_iter()
.map(|(start, stop)| (anchor + start, anchor + stop)),
);
anchor = end;
}
assert_eq!(whole, assembled);
}
#[test]
fn boundary_search_never_looks_past_one_upload_part() {
let data = structured_bytes(3 * CHUNK_ANCHOR_BYTES, 13);
let mut altered = data.clone();
altered[2 * CHUNK_ANCHOR_BYTES..]
.copy_from_slice(&structured_bytes(CHUNK_ANCHOR_BYTES, 71));
let first_two = |bytes: &[u8]| {
chunk_ranges(bytes)
.into_iter()
.filter(|(start, _)| *start < 2 * CHUNK_ANCHOR_BYTES)
.collect::<Vec<_>>()
};
assert_eq!(first_two(&data), first_two(&altered));
}
}