Skip to main content

xet_data/deduplication/
chunking.rs

1use std::cmp::min;
2use std::io::{Read, Seek, SeekFrom};
3
4use bytes::Bytes;
5use more_asserts::{debug_assert_ge, debug_assert_le};
6
7use super::Chunk;
8use super::constants::{MAXIMUM_CHUNK_MULTIPLIER, MINIMUM_CHUNK_DIVISOR, TARGET_CHUNK_SIZE};
9
10lazy_static::lazy_static! {
11    /// The maximum chunk size, calculated from the configurable constants above
12    pub static ref MAX_CHUNK_SIZE: usize = (*TARGET_CHUNK_SIZE) * (*MAXIMUM_CHUNK_MULTIPLIER);
13}
14
15/// Chunk Generator given an input stream. Do not use directly.
16/// Use `chunk_target_default`.
17pub struct Chunker {
18    // configs
19    hash: gearhash::Hasher<'static>,
20    minimum_chunk: usize,
21    maximum_chunk: usize,
22    mask: u64,
23
24    // generator state
25    chunkbuf: Vec<u8>,
26}
27
28impl Default for Chunker {
29    fn default() -> Self {
30        Self::new(*TARGET_CHUNK_SIZE)
31    }
32}
33
34impl Chunker {
35    pub fn new(target_chunk_size: usize) -> Self {
36        assert_eq!(target_chunk_size.count_ones(), 1);
37
38        // Some of the logic only works if the target_chunk_size is greater than the
39        // window size of the hash.
40        assert!(target_chunk_size > 64);
41
42        // note the strict lesser than. Combined with count_ones() == 1,
43        // this limits to 2^31
44        assert!(target_chunk_size < u32::MAX as usize);
45
46        let mask = (target_chunk_size - 1) as u64;
47
48        // we will like to shift the mask left by a bunch since the right
49        // bits of the gear hash are affected by only a small number of bytes
50        // really. we just shift it all the way left.
51        let mask = mask << mask.leading_zeros();
52        let minimum_chunk = target_chunk_size / *MINIMUM_CHUNK_DIVISOR;
53        let maximum_chunk = target_chunk_size * *MAXIMUM_CHUNK_MULTIPLIER;
54
55        assert!(maximum_chunk > minimum_chunk);
56
57        let hash = gearhash::Hasher::default();
58
59        Chunker {
60            hash,
61            minimum_chunk,
62            maximum_chunk,
63            mask,
64            // generator state init
65            chunkbuf: Vec::with_capacity(maximum_chunk),
66        }
67    }
68
69    /// Create a chunker with custom min chunk sizes.
70    /// Only used by the partitioner which has special requirements.
71    fn new_with_min(target_chunk_size: usize, min_chunk_size: usize) -> Self {
72        let mut chunker = Self::new(target_chunk_size);
73        chunker.minimum_chunk = min_chunk_size;
74        chunker
75    }
76
77    /// Looks for the next chunk boundary in the data.  Assumes that whatever is in the current
78    /// state has been prepended to the current data.  If a boundary cannot be found based on the
79    /// current amount of data, then None is returned.
80    #[inline]
81    pub fn next_boundary(&mut self, data: &[u8]) -> Option<usize> {
82        const HASH_WINDOW_SIZE: usize = 64;
83        let n_bytes = data.len();
84
85        if n_bytes == 0 {
86            return None;
87        }
88
89        let previous_len = self.chunkbuf.len();
90        let mut cur_index = 0;
91        let mut create_chunk = false;
92
93        // skip the minimum chunk size
94        // and noting that the hash has a window size of 64
95        // so we should be careful to skip only minimum_chunk - 64 - 1
96        if previous_len + HASH_WINDOW_SIZE < self.minimum_chunk {
97            let skip = min(self.minimum_chunk - previous_len - HASH_WINDOW_SIZE - 1, n_bytes);
98            cur_index += skip;
99        }
100
101        // If we have a lot of data, don't read all the way to the end when we'll stop reading
102        // at the maximum chunk boundary.
103        let read_end = n_bytes.min(cur_index + self.maximum_chunk - previous_len);
104
105        loop {
106            if let Some(next_match) = self.hash.next_match(&data[cur_index..read_end], self.mask) {
107                cur_index += next_match;
108
109                // If we trigger a boundary before the end, create a chunk.
110
111                // We must enforce that the next boundary is actually past the minimum chunk size.
112                // Because of how the rolling hash is computed, bytes before HASH_WINDOW_SIZE don't affect the hash,
113                // so with the above skip we depend on it running for at least HASH_WINDOW_SIZE bytes before triggering
114                // a boundary.   However, in rare occurrences, there can be a boundary triggered before HASH_WINDOW_SIZE
115                // bytes have been processed, which means the boundary is triggered based on the previous state of the
116                // hasher rather than on the current chunk content.  Thus we ensure this can't happen by ensuring that
117                // we have processed at least HASH_WINDOW_SIZE bytes.
118                if cur_index + previous_len < self.minimum_chunk {
119                    continue;
120                }
121
122                create_chunk = true;
123            } else {
124                cur_index = read_end;
125            }
126
127            break;
128        }
129
130        // if we hit maximum chunk we must create a chunk
131        if cur_index + previous_len >= self.maximum_chunk {
132            cur_index = self.maximum_chunk - previous_len;
133            create_chunk = true;
134        }
135
136        if create_chunk {
137            self.hash.set_hash(0); // Reset for the next time.
138            debug_assert_ge!(cur_index + previous_len, self.minimum_chunk);
139            debug_assert_le!(cur_index + previous_len, self.maximum_chunk);
140            Some(cur_index)
141        } else {
142            None
143        }
144    }
145
146    fn reset_state(&mut self) {
147        // Strictly speaking, this is unnecessary, as we should always hash 64 bytes out making the previous state
148        // of the hasher irrelevant.  However, this explicitly declares we're resetting things to the
149        // initial state.
150        self.hash.set_hash(0);
151        debug_assert!(self.chunkbuf.is_empty());
152    }
153
154    /// Process more data; this is a continuation of any data from before when calls were
155    ///
156    /// Returns the next chunk, if available, and the amount of data that was digested.
157    ///
158    /// If is_final is true, then it is assumed that no more data after this block will come,
159    /// and any data currently present and at the end will be put into a final chunk.
160    pub fn next(&mut self, data: &[u8], is_final: bool) -> (Option<Chunk>, usize) {
161        let (chunk_data, consume): (Bytes, usize) = {
162            if let Some(next_boundary) = self.next_boundary(data) {
163                if self.chunkbuf.is_empty() {
164                    (Bytes::copy_from_slice(&data[..next_boundary]), next_boundary)
165                } else {
166                    self.chunkbuf.extend_from_slice(&data[..next_boundary]);
167                    (std::mem::take(&mut self.chunkbuf).into(), next_boundary)
168                }
169            } else if is_final {
170                // Put the rest of the data in the chunkbuf.
171                let r = if self.chunkbuf.is_empty() {
172                    (Bytes::copy_from_slice(data), data.len())
173                } else {
174                    self.chunkbuf.extend_from_slice(data);
175                    (std::mem::take(&mut self.chunkbuf).into(), data.len())
176                };
177
178                if is_final {
179                    self.reset_state();
180                }
181
182                r
183            } else {
184                self.chunkbuf.extend_from_slice(data);
185                return (None, data.len());
186            }
187        };
188
189        // Special case this specific case.
190        if chunk_data.is_empty() {
191            return (None, 0);
192        }
193
194        (Some(Chunk::new(chunk_data)), consume)
195    }
196
197    /// Keeps chunking until no more chunks can be reliably produced, returning a
198    /// vector of the resulting chunks.  
199    pub fn next_block(&mut self, data: &[u8], is_final: bool) -> Vec<Chunk> {
200        let mut ret = Vec::new();
201
202        let mut pos = 0;
203        loop {
204            debug_assert!(pos <= data.len());
205            if pos == data.len() {
206                if is_final {
207                    self.reset_state();
208                }
209
210                return ret;
211            }
212
213            let (maybe_chunk, bytes_consumed) = self.next(&data[pos..], is_final);
214
215            if let Some(chunk) = maybe_chunk {
216                ret.push(chunk);
217            }
218
219            pos += bytes_consumed;
220        }
221    }
222
223    /// Keeps chunking until no more chunks can be reliably produced, returning a
224    /// vector of the resulting chunks.
225    ///
226    /// The data is inserted here as a Bytes object, which means that no copying of the data
227    /// is performed except at the boundaries.  The resulting chunks then end up each holding
228    /// a reference to the original data object, which will not be deallocated until all the
229    /// original bytes are gone.
230    pub fn next_block_bytes(&mut self, data: &Bytes, is_final: bool) -> Vec<Chunk> {
231        let mut ret = Vec::new();
232
233        let mut pos = 0;
234
235        // In this case, we have to perform a single cut using the old method,
236        // which would copy the data.
237        if !self.chunkbuf.is_empty() {
238            let (maybe_chunk, skip_idx) = self.next(data, is_final);
239            if let Some(chunk) = maybe_chunk {
240                ret.push(chunk);
241            }
242            pos = skip_idx;
243        }
244
245        while pos < data.len() {
246            let maybe_next_boundary = self.next_boundary(&data[pos..]);
247
248            if let Some(chunk_size) = maybe_next_boundary {
249                let next_pos = pos + chunk_size;
250                ret.push(Chunk::new(data.slice(pos..next_pos)));
251                pos = next_pos;
252            } else {
253                // No more chunks in this block.
254                if is_final {
255                    ret.push(Chunk::new(data.slice(pos..)));
256                } else {
257                    self.chunkbuf.extend_from_slice(&data[pos..]);
258                }
259                break;
260            }
261        }
262
263        if is_final {
264            self.reset_state();
265        }
266
267        ret
268    }
269
270    // Finishes, returning the final chunk if one exists, and resets the hasher to
271    pub fn finish(&mut self) -> Option<Chunk> {
272        self.next(&[], true).0
273    }
274}
275
276/// Find valid partition points in a file where we can
277/// chunk in parallel. Returns the start points of each partition
278/// (i.e. file offset 0 is always the first entry, and `file_size`
279/// is never in the result).
280/// Note that reader position is modified and not restored.
281///
282/// partition_scan_bytes is the number of bytes to scan at each
283/// proposed partition boundary in search of a valid chunk.
284///
285/// Partition alignment is guaranteed by the hash warmup fix: the
286/// chunker feeds `min_chunk - 64 - 1` bytes before scanning for
287/// boundaries, ensuring the gear hash window is fully warmed (purely
288/// data-dependent) at all accepted trigger positions. This function
289/// additionally verifies the absence of hidden triggers by re-chunking
290/// with `min_chunk = 0`. See `parallel chunking.lyx` for the proof.
291///
292/// For finding stable chunk boundaries from existing chunk boundaries (without
293/// data access), see [`next_stable_chunk_boundary`].
294pub fn find_partitions<R: Read + Seek>(
295    reader: &mut R,
296    file_size: usize,
297    target_chunk_size: usize,
298    min_partition_size: usize,
299    partition_scan_bytes: usize,
300) -> std::io::Result<Vec<usize>> {
301    assert!(min_partition_size > 0);
302    let mut partitions: Vec<usize> = Vec::new();
303    partitions.push(0);
304    // minimum chunk must be at least the hash window size.
305    // the way the chunker works, the minimum may be up to
306    // target_min_chunk_size - 64
307    let minimum_chunk = target_chunk_size / *MINIMUM_CHUNK_DIVISOR;
308    let maximum_chunk = target_chunk_size * *MAXIMUM_CHUNK_MULTIPLIER;
309
310    assert!(minimum_chunk > 64);
311
312    if maximum_chunk >= min_partition_size {
313        return Ok(partitions);
314    }
315    let mut buf = vec![0u8; partition_scan_bytes];
316    let mut curpos: usize = 0;
317    // we jump curpos forward by min_partition_size
318    // and read *PARALLEL_CHUNKING_PARTITION_SCAN_BYTES bytes
319    // and try to find a partition boundary condition.
320    //
321    // We should also make sure There should also be at least
322    // min_partition_size bytes remaining at curpos so that
323    // we do not make a teeny tiny partition.
324    while curpos < file_size {
325        curpos += min_partition_size;
326        // there are not enough bytes to make a full partition
327        // or not enough bytes to scan for a partition
328        if curpos + min_partition_size >= file_size || curpos + partition_scan_bytes >= file_size {
329            break;
330        }
331        // read and chunk the scan bytes
332        reader.seek(SeekFrom::Start(curpos as u64))?;
333        reader.read_exact(&mut buf)?;
334        let mut chunker = Chunker::new_with_min(target_chunk_size, 0);
335        // TODO: there is a definite optimization here
336        // as we really only need the chunk lengths and not the data
337        let chunks = chunker.next_block(&buf, false);
338        if chunks.is_empty() {
339            continue;
340        }
341        // skip the first chunk
342        let mut offset = chunks[0].data.len();
343        offset += chunks[1].data.len();
344        for i in 2..chunks.len() {
345            let cprev = chunks[i - 1].data.len();
346            let c = chunks[i].data.len();
347            offset += chunks[i].data.len();
348            if cprev > minimum_chunk
349                && cprev < maximum_chunk - minimum_chunk
350                && c > minimum_chunk
351                && c < maximum_chunk - minimum_chunk
352            {
353                // we have a valid partition at this position
354                partitions.push(curpos + offset);
355                break;
356            }
357        }
358    }
359    Ok(partitions)
360}
361
362// Re-exported from xet_core_structures where the canonical implementation lives,
363// so that downstream users of xet_data::deduplication::next_stable_chunk_boundary
364// continue to work without a source change.
365pub use xet_core_structures::xorb_object::constants::next_stable_chunk_boundary;
366
367#[cfg(test)]
368mod tests {
369    use std::collections::HashSet;
370    use std::io::Cursor;
371
372    use rand::rngs::StdRng;
373    use rand::{RngExt, SeedableRng};
374
375    use super::*;
376
377    /// A helper to create random test data using a specified `seed` and `len`.
378    /// Using a fixed seed ensures tests are reproducible.
379    fn make_test_data(seed: u64, len: usize) -> Vec<u8> {
380        let mut rng = StdRng::seed_from_u64(seed);
381        let mut data = vec![0; len];
382        rng.fill(&mut data[..]);
383        data
384    }
385
386    fn check_chunks_equal(chunks: &[Chunk], data: &[u8]) {
387        // Validate all the chunks are exact.
388        let mut new_vec = Vec::with_capacity(10000);
389        for c in chunks.iter() {
390            new_vec.extend_from_slice(&c.data[..]);
391        }
392
393        assert!(new_vec == data);
394    }
395
396    // A chunker that wraps two versions of the Chunker class,
397    // exposing next_block but then internally testing next_bytes_block and next_block and
398    // verifying the output is identical.
399    #[derive(Default)]
400    struct ChunkerTestWrapper {
401        chunker_chunks: Chunker,
402        chunker_bytes: Chunker,
403    }
404
405    impl ChunkerTestWrapper {
406        fn new(target_chunk_size: usize) -> Self {
407            ChunkerTestWrapper {
408                chunker_chunks: Chunker::new(target_chunk_size),
409                chunker_bytes: Chunker::new(target_chunk_size),
410            }
411        }
412
413        fn next_block(&mut self, data: &[u8], is_final: bool) -> Vec<Chunk> {
414            let chunks = self.chunker_chunks.next_block(data, is_final);
415            let bytes_chunks = self.chunker_bytes.next_block_bytes(&Bytes::copy_from_slice(data), is_final);
416
417            // Check that the two match.
418            assert_eq!(chunks.len(), bytes_chunks.len());
419            for (c1, c2) in chunks.iter().zip(bytes_chunks.iter()) {
420                assert_eq!(c1.data, c2.data);
421            }
422
423            chunks
424        }
425
426        fn next_chunk(&mut self, data: &[u8], is_final: bool) -> (Option<Chunk>, usize) {
427            let (chunk, consumed) = self.chunker_chunks.next(data, is_final);
428            let (bytes_chunk, bytes_consumed) = self.chunker_bytes.next(&Bytes::copy_from_slice(data), is_final);
429
430            // Check that the two match.
431            if let Some(c) = &chunk {
432                assert_eq!(c.data, bytes_chunk.unwrap().data);
433            } else {
434                assert!(bytes_chunk.is_none());
435            }
436
437            (chunk, consumed.max(bytes_consumed))
438        }
439    }
440
441    #[test]
442    fn test_empty_data_no_chunk_until_final() {
443        let mut chunker = ChunkerTestWrapper::new(128);
444
445        // Passing empty slice without final => no chunk
446        let (chunk, consumed) = chunker.next_chunk(&[], false);
447        assert!(chunk.is_none());
448        assert_eq!(consumed, 0);
449
450        // Passing empty slice again with is_final = true => no leftover data, so no chunk
451        let (chunk, consumed) = chunker.next_chunk(&[], true);
452        assert!(chunk.is_none());
453        assert_eq!(consumed, 0);
454    }
455
456    #[test]
457    fn test_data_smaller_than_minimum_no_boundary() {
458        let mut chunker = ChunkerTestWrapper::new(128);
459
460        // Create a small random data buffer. For example, length=3.
461        let data = make_test_data(0, 63);
462
463        // We expect no chunk until we finalize, because there's not enough data
464        // to trigger a boundary, nor to reach the maximum chunk size.
465        let (chunk, consumed) = chunker.next_chunk(&data, false);
466        assert!(chunk.is_none());
467        assert_eq!(consumed, data.len());
468
469        // Now finalize: we expect a chunk with the leftover data
470        let (chunk, consumed) = chunker.next_chunk(&[], true);
471        assert!(chunk.is_some());
472        assert_eq!(consumed, 0);
473
474        let chunk = chunk.unwrap();
475        assert_eq!(chunk.data.len(), 63);
476        assert_eq!(&chunk.data[..], &data[..], "Chunk should contain exactly what was passed in");
477    }
478
479    #[test]
480    fn test_multiple_chunks_produced() {
481        let mut chunker = ChunkerTestWrapper::new(128);
482
483        // Produce 100 bytes of random data
484        let data = make_test_data(42, 10000);
485
486        // Pass everything at once, final = true
487        let chunks = chunker.next_block(&data, true);
488        assert!(!chunks.is_empty());
489
490        check_chunks_equal(&chunks, &data);
491    }
492
493    #[test]
494    fn test_repeated_calls_partial_consumption() {
495        // We'll feed in two pieces of data to ensure partial consumption
496
497        let data = make_test_data(42, 10000);
498
499        let mut chunks_1 = Vec::new();
500
501        let mut pos = 0;
502        let mut chunker = ChunkerTestWrapper::new(128);
503
504        while pos < data.len() {
505            for i in 0..16 {
506                let next_pos = (pos + i).min(data.len());
507                chunks_1.append(&mut chunker.next_block(&data[pos..next_pos], next_pos == data.len()));
508                pos = next_pos;
509            }
510        }
511
512        check_chunks_equal(&chunks_1, &data);
513
514        // Now, rechunk with all at once and make sure it's equal.
515        let chunks_2 = ChunkerTestWrapper::new(128).next_block(&data, true);
516
517        assert_eq!(chunks_1, chunks_2);
518    }
519
520    #[test]
521    fn test_exact_maximum_chunk() {
522        // If the data hits the maximum chunk size exactly, we should force a boundary.
523        // For target_chunk_size = 128, if MAXIMUM_CHUNK_MULTIPLIER = 2, then max = 256.
524        // Adjust if your constants differ.
525        let mut chunker = ChunkerTestWrapper::new(512);
526
527        // Use constant data
528        let data = vec![0; 8 * *MAXIMUM_CHUNK_MULTIPLIER * 512];
529
530        let chunks = chunker.next_block(&data, true);
531
532        assert_eq!(chunks.len(), 8);
533
534        for c in chunks.iter() {
535            assert_eq!(c.data.len(), *MAXIMUM_CHUNK_MULTIPLIER * 512);
536        }
537    }
538
539    #[test]
540    fn test_partition() {
541        for _i in 1..5 {
542            let data = make_test_data(42, 1000000);
543            let mut chunker = Chunker::new(1024);
544            let chunks = chunker.next_block(&data, true);
545            let mut chunk_offsets = HashSet::new();
546            let mut offset = 0;
547            eprintln!("{:?}", chunker.minimum_chunk);
548            for i in 0..chunks.len() {
549                chunk_offsets.insert(offset);
550                offset += chunks[i].data.len();
551            }
552
553            let partitions =
554                find_partitions(&mut Cursor::new(&mut data.as_slice()), data.len(), 1024, 100000, 10000).unwrap();
555            assert!(partitions.len() > 1);
556            for i in 0..partitions.len() {
557                assert!(chunk_offsets.contains(&partitions[i]));
558            }
559        }
560    }
561
562    /// Simple SplitMix64-based deterministic random number generator.
563    /// Portable to C, Python, etc. (see https://prng.di.unimi.it/splitmix64.c)
564    fn splitmix64_next(state: &mut u64) -> u64 {
565        *state = state.wrapping_add(0x9E3779B97F4A7C15);
566        let mut z = *state;
567        z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
568        z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
569        z ^ (z >> 31)
570    }
571
572    fn create_random_data(n: usize, seed: u64) -> Vec<u8> {
573        // This test will actually need to be run in different environments, so to generate
574        // the table below, create random data using a simple SplitMix rng that can be ported here
575        // as is without depending on other po
576        let mut ret = Vec::with_capacity(n + 7);
577
578        let mut state = seed;
579
580        while ret.len() < n {
581            let next_u64 = splitmix64_next(&mut state);
582            ret.extend_from_slice(&next_u64.to_le_bytes());
583        }
584
585        // Has extra bits on there since we're adding in blocks of 8.
586        ret.resize(n, 0);
587
588        ret
589    }
590
591    fn get_chunk_boundaries(chunks: &[Chunk]) -> Vec<usize> {
592        chunks
593            .iter()
594            .scan(0, |state, chunk| {
595                *state += chunk.data.len();
596                Some(*state)
597            })
598            .collect()
599    }
600
601    #[test]
602    fn test_chunk_boundaries() {
603        let data = create_random_data(256000, 1);
604
605        // Now, run the chunks through the default chunker.
606        let chunks = ChunkerTestWrapper::default().next_block(&data, true);
607
608        // Get the boundaries indices as determined by the size of the chunks above.
609        let ref_chunk_boundaries: Vec<usize> = get_chunk_boundaries(&chunks);
610
611        // Test that it's correct across different chunk varieties.
612        for add_size in [1, 37, 255] {
613            let mut chunker = Chunker::default();
614
615            // Add repeatedly in blocks of add_size, appending to alt_chunks
616            let mut alt_chunks = Vec::with_capacity(chunks.len());
617
618            let mut pos = 0;
619            while pos < data.len() {
620                let next_pos = (pos + add_size).min(data.len());
621                let next_chunk = chunker.next_block(&data[pos..next_pos], next_pos == data.len());
622                alt_chunks.extend(next_chunk);
623                pos = next_pos;
624            }
625
626            let alt_boundaries = get_chunk_boundaries(&alt_chunks);
627
628            assert_eq!(alt_boundaries, ref_chunk_boundaries);
629        }
630    }
631
632    #[test]
633    fn test_correctness_1mb_random_data() {
634        // Test this data.
635        let data = create_random_data(1000000, 0);
636
637        // Uncomment these to create the lines below:
638        // eprintln!("(data[0], {});", data[0] as usize);
639        // eprintln!("(data[127], {});", data[127] as usize);
640        // eprintln!("(data[111111], {});", data[111111] as usize);
641
642        assert_eq!(data[0], 175);
643        assert_eq!(data[127], 132);
644        assert_eq!(data[111111], 118);
645
646        // Now, run the chunks through the default chunker.
647        let chunks = ChunkerTestWrapper::default().next_block(&data, true);
648
649        // Get the boundaries indices as determined by the size of the chunks above.
650        let chunk_boundaries: Vec<usize> = get_chunk_boundaries(&chunks);
651
652        // Uncomment this to create the line below.
653        // eprintln!("assert_eq!(chunk_boundaries, vec!{chunk_boundaries:?})");
654        assert_eq!(
655            chunk_boundaries,
656            vec![
657                84493, 134421, 144853, 243318, 271793, 336457, 467529, 494581, 582000, 596735, 616815, 653164, 678202,
658                724510, 815591, 827760, 958832, 991092, 1000000
659            ]
660        );
661    }
662
663    #[test]
664    fn test_correctness_1mb_const_data() {
665        // Test this data.
666        let data = vec![59u8; 1000000];
667
668        // Now, run the chunks through the default chunker.
669        let chunks = ChunkerTestWrapper::default().next_block(&data, true);
670
671        // Get the boundaries indices as determined by the size of the chunks above.
672        let chunk_boundaries: Vec<usize> = get_chunk_boundaries(&chunks);
673
674        // Uncomment this to create the line below.
675        // eprintln!("assert_eq!(chunk_boundaries, vec!{chunk_boundaries:?})");
676        assert_eq!(chunk_boundaries, vec![131072, 262144, 393216, 524288, 655360, 786432, 917504, 1000000])
677    }
678
679    fn get_triggering_base_data(n: usize, padding: usize) -> Vec<u8> {
680        // This pattern is known to trigger the boundary detection in the chunker, so repeat it to test the
681        // correctness of the minimum chunk size processing.
682        let mut data = vec![
683            154, 52, 42, 34, 159, 75, 126, 224, 70, 236, 12, 196, 79, 236, 178, 124, 127, 50, 99, 178, 44, 176, 174,
684            126, 250, 235, 205, 174, 252, 122, 35, 10, 20, 101, 214, 69, 193, 8, 115, 105, 158, 228, 120, 111, 136,
685            162, 198, 251, 211, 183, 253, 252, 164, 147, 63, 16, 186, 162, 117, 23, 170, 36, 205, 187, 174, 76, 210,
686            174, 211, 175, 12, 173, 145, 59, 2, 70, 222, 181, 159, 227, 182, 156, 189, 51, 226, 106, 24, 50, 183, 157,
687            140, 10, 8, 23, 212, 70, 10, 234, 23, 33, 219, 254, 39, 236, 70, 49, 191, 116, 9, 115, 15, 101, 26, 159,
688            165, 220, 15, 170, 56, 125, 92, 163, 94, 235, 38, 40, 49, 81,
689        ];
690
691        // Add padding so we can comprehensively test the nuances of boundaries.
692        data.resize(data.len() + padding, 0u8);
693
694        // Repeat the above pattern until we've filled out n bytes.
695        while data.len() < n {
696            let n_take = (n - data.len()).min(data.len());
697            data.extend_from_within(0..n_take);
698        }
699
700        data
701    }
702
703    #[test]
704    fn test_correctness_100kb_hitting_data() {
705        // To ensure we've checked all the nuances of dealing with minimum chunk boundaries,
706        // and with the correct chunks as well, run through all the different options with the padding,
707        // checking each one.  With this, then, we have a pattern that hits once per pattern with varying
708        // bits between the widths.
709
710        let mut data_sample_at_11111 = [0u8; 128];
711        let mut ref_cb = vec![Vec::new(); 128];
712
713        data_sample_at_11111[0] = 236;
714        ref_cb[0] = vec![8256, 16448, 24640, 32832, 41024, 49216, 57408, 65536];
715        data_sample_at_11111[1] = 50;
716        ref_cb[1] = vec![8320, 16576, 24832, 33088, 41344, 49600, 57856, 65536];
717        data_sample_at_11111[2] = 36;
718        ref_cb[2] = vec![8254, 16574, 24894, 33214, 41534, 49854, 58174, 65536];
719        data_sample_at_11111[3] = 116;
720        ref_cb[3] = vec![8317, 16570, 24823, 33076, 41329, 49582, 57835, 65536];
721        data_sample_at_11111[4] = 126;
722        ref_cb[4] = vec![8248, 16564, 24880, 33196, 41512, 49828, 58144, 65536];
723        data_sample_at_11111[5] = 145;
724        ref_cb[5] = vec![8310, 16556, 24802, 33048, 41294, 49540, 57786, 65536];
725        data_sample_at_11111[6] = 235;
726        ref_cb[6] = vec![8238, 16546, 24854, 33162, 41470, 49778, 58086, 65536];
727        data_sample_at_11111[7] = 228;
728        ref_cb[7] = vec![8299, 16534, 24769, 33004, 41239, 49474, 57709, 65536];
729        data_sample_at_11111[8] = 70;
730        ref_cb[8] = vec![8224, 16520, 24816, 33112, 41408, 49704, 58000, 65536];
731        data_sample_at_11111[9] = 178;
732        ref_cb[9] = vec![8284, 16504, 24724, 32944, 41164, 49384, 57604, 65536];
733        data_sample_at_11111[10] = 173;
734        ref_cb[10] = vec![8206, 16486, 24766, 33046, 41326, 49606, 57886, 65536];
735        data_sample_at_11111[11] = 0;
736        ref_cb[11] = vec![8265, 16466, 24667, 32868, 41069, 49270, 57471, 65536];
737        data_sample_at_11111[12] = 252;
738        ref_cb[12] = vec![8324, 16584, 24844, 33104, 41364, 49624, 57884, 65536];
739        data_sample_at_11111[13] = 159;
740        ref_cb[13] = vec![8242, 16561, 24880, 33199, 41518, 49837, 58156, 65536];
741        data_sample_at_11111[14] = 69;
742        ref_cb[14] = vec![8300, 16536, 24772, 33008, 41244, 49480, 57716, 65536];
743        data_sample_at_11111[15] = 219;
744        ref_cb[15] = vec![8215, 16509, 24803, 33097, 41391, 49685, 57979, 65536];
745        data_sample_at_11111[16] = 126;
746        ref_cb[16] = vec![8272, 16480, 24688, 32896, 41104, 49312, 57520, 65536];
747        data_sample_at_11111[17] = 10;
748        ref_cb[17] = vec![8329, 16594, 24859, 33124, 41389, 49654, 57919, 65536];
749        data_sample_at_11111[18] = 124;
750        ref_cb[18] = vec![8240, 16562, 24884, 33206, 41528, 49850, 58172, 65536];
751        data_sample_at_11111[19] = 24;
752        ref_cb[19] = vec![8296, 16528, 24760, 32992, 41224, 49456, 57688, 65536];
753        data_sample_at_11111[20] = 196;
754        ref_cb[20] = vec![8204, 16492, 24780, 33068, 41356, 49644, 57932, 65536];
755        data_sample_at_11111[21] = 106;
756        ref_cb[21] = vec![8259, 16454, 24649, 32844, 41039, 49234, 57429, 65536];
757        data_sample_at_11111[22] = 196;
758        ref_cb[22] = vec![8314, 16564, 24814, 33064, 41314, 49564, 57814, 65536];
759        data_sample_at_11111[23] = 183;
760        ref_cb[23] = vec![8218, 16523, 24828, 33133, 41438, 49743, 58048, 65536];
761        data_sample_at_11111[24] = 124;
762        ref_cb[24] = vec![8272, 16480, 24688, 32896, 41104, 49312, 57520, 65536];
763        data_sample_at_11111[25] = 70;
764        ref_cb[25] = vec![8326, 16588, 24850, 33112, 41374, 49636, 57898, 65536];
765        data_sample_at_11111[26] = 126;
766        ref_cb[26] = vec![8226, 16542, 24858, 33174, 41490, 49806, 58122, 65536];
767        data_sample_at_11111[27] = 191;
768        ref_cb[27] = vec![8279, 16494, 24709, 32924, 41139, 49354, 57569, 65536];
769        data_sample_at_11111[28] = 69;
770        ref_cb[28] = vec![8332, 16600, 24868, 33136, 41404, 49672, 57940, 65536];
771        data_sample_at_11111[29] = 163;
772        ref_cb[29] = vec![8228, 16549, 24870, 33191, 41512, 49833, 58154, 65536];
773        data_sample_at_11111[30] = 252;
774        ref_cb[30] = vec![8280, 16496, 24712, 32928, 41144, 49360, 57576, 65536];
775        data_sample_at_11111[31] = 0;
776        ref_cb[31] = vec![8332, 16600, 24868, 33136, 41404, 49672, 57940, 65536];
777        data_sample_at_11111[32] = 173;
778        ref_cb[32] = vec![8224, 16544, 24864, 33184, 41504, 49824, 58144, 65536];
779        data_sample_at_11111[33] = 42;
780        ref_cb[33] = vec![8275, 16486, 24697, 32908, 41119, 49330, 57541, 65536];
781        data_sample_at_11111[34] = 70;
782        ref_cb[34] = vec![8326, 16588, 24850, 33112, 41374, 49636, 57898, 65536];
783        data_sample_at_11111[35] = 174;
784        ref_cb[35] = vec![8214, 16527, 24840, 33153, 41466, 49779, 58092, 65536];
785        data_sample_at_11111[36] = 235;
786        ref_cb[36] = vec![8264, 16464, 24664, 32864, 41064, 49264, 57464, 65536];
787        data_sample_at_11111[37] = 186;
788        ref_cb[37] = vec![8314, 16564, 24814, 33064, 41314, 49564, 57814, 65536];
789        data_sample_at_11111[38] = 0;
790        ref_cb[38] = vec![8198, 16498, 24798, 33098, 41398, 49698, 57998, 65536];
791        data_sample_at_11111[39] = 157;
792        ref_cb[39] = vec![8247, 16597, 24947, 33297, 41647, 49997, 58347, 65536];
793        data_sample_at_11111[40] = 126;
794        ref_cb[40] = vec![8296, 16528, 24760, 32992, 41224, 49456, 57688, 65536];
795        data_sample_at_11111[41] = 49;
796        ref_cb[41] = vec![8345, 16626, 24907, 33188, 41469, 49750, 58031, 65536];
797        data_sample_at_11111[42] = 36;
798        ref_cb[42] = vec![8224, 16554, 24884, 33214, 41544, 49874, 58204, 65536];
799        data_sample_at_11111[43] = 0;
800        ref_cb[43] = vec![8272, 16480, 24688, 32896, 41104, 49312, 57520, 65536];
801        data_sample_at_11111[44] = 236;
802        ref_cb[44] = vec![8320, 16576, 24832, 33088, 41344, 49600, 57856, 65536];
803        data_sample_at_11111[45] = 105;
804        ref_cb[45] = vec![8195, 16499, 24803, 33107, 41411, 49715, 58019, 65536];
805        data_sample_at_11111[46] = 0;
806        ref_cb[46] = vec![8242, 16594, 24946, 33298, 41650, 50002, 58354, 65536];
807        data_sample_at_11111[47] = 24;
808        ref_cb[47] = vec![8289, 16514, 24739, 32964, 41189, 49414, 57639, 65536];
809        data_sample_at_11111[48] = 126;
810        ref_cb[48] = vec![8336, 16608, 24880, 33152, 41424, 49696, 57968, 65536];
811        data_sample_at_11111[49] = 0;
812        ref_cb[49] = vec![8206, 16525, 24844, 33163, 41482, 49801, 58120, 65536];
813        data_sample_at_11111[50] = 70;
814        ref_cb[50] = vec![8252, 16618, 24984, 33350, 41716, 50082, 58448, 65536];
815        data_sample_at_11111[51] = 236;
816        ref_cb[51] = vec![8298, 16532, 24766, 33000, 41234, 49468, 57702, 65536];
817        data_sample_at_11111[52] = 0;
818        ref_cb[52] = vec![8344, 16624, 24904, 33184, 41464, 49744, 58024, 65536];
819        data_sample_at_11111[53] = 12;
820        ref_cb[53] = vec![8209, 16535, 24861, 33187, 41513, 49839, 58165, 65536];
821        data_sample_at_11111[54] = 236;
822        ref_cb[54] = vec![8254, 16626, 24998, 33370, 41742, 50114, 58486, 65536];
823        data_sample_at_11111[55] = 0;
824        ref_cb[55] = vec![8299, 16534, 24769, 33004, 41239, 49474, 57709, 65536];
825        data_sample_at_11111[56] = 173;
826        ref_cb[56] = vec![8344, 16624, 24904, 33184, 41464, 49744, 58024, 65536];
827        data_sample_at_11111[57] = 196;
828        ref_cb[57] = vec![8204, 16529, 24854, 33179, 41504, 49829, 58154, 65536];
829        data_sample_at_11111[58] = 0;
830        ref_cb[58] = vec![8248, 16618, 24988, 33358, 41728, 50098, 58468, 65536];
831        data_sample_at_11111[59] = 159;
832        ref_cb[59] = vec![8292, 16520, 24748, 32976, 41204, 49432, 57660, 65536];
833        data_sample_at_11111[60] = 178;
834        ref_cb[60] = vec![8336, 16608, 24880, 33152, 41424, 49696, 57968, 65536];
835        data_sample_at_11111[61] = 0;
836        ref_cb[61] = vec![8380, 16696, 25012, 33328, 41644, 49960, 58276, 65536];
837        data_sample_at_11111[62] = 10;
838        ref_cb[62] = vec![8234, 16594, 24954, 33314, 41674, 50034, 58394, 65536];
839        data_sample_at_11111[63] = 101;
840        ref_cb[63] = vec![8277, 16490, 24703, 32916, 41129, 49342, 57555, 65536];
841        data_sample_at_11111[64] = 0;
842        ref_cb[64] = vec![8320, 16576, 24832, 33088, 41344, 49600, 57856, 65536];
843        data_sample_at_11111[65] = 15;
844        ref_cb[65] = vec![8363, 16662, 24961, 33260, 41559, 49858, 58157, 65536];
845        data_sample_at_11111[66] = 147;
846        ref_cb[66] = vec![8212, 16554, 24896, 33238, 41580, 49922, 58264, 65536];
847        data_sample_at_11111[67] = 0;
848        ref_cb[67] = vec![8254, 16639, 25024, 33409, 41794, 50179, 58564, 65536];
849        data_sample_at_11111[68] = 0;
850        ref_cb[68] = vec![8296, 16528, 24760, 32992, 41224, 49456, 57688, 65536];
851        data_sample_at_11111[69] = 227;
852        ref_cb[69] = vec![8338, 16612, 24886, 33160, 41434, 49708, 57982, 65536];
853        data_sample_at_11111[70] = 126;
854        ref_cb[70] = vec![8380, 16696, 25012, 33328, 41644, 49960, 58276, 65536];
855        data_sample_at_11111[71] = 0;
856        ref_cb[71] = vec![8223, 16581, 24939, 33297, 41655, 50013, 58371, 65536];
857        data_sample_at_11111[72] = 101;
858        ref_cb[72] = vec![8264, 16464, 24664, 32864, 41064, 49264, 57464, 65536];
859        data_sample_at_11111[73] = 186;
860        ref_cb[73] = vec![8305, 16546, 24787, 33028, 41269, 49510, 57751, 65536];
861        data_sample_at_11111[74] = 52;
862        ref_cb[74] = vec![8346, 16628, 24910, 33192, 41474, 49756, 58038, 65536];
863        data_sample_at_11111[75] = 0;
864        ref_cb[75] = vec![8387, 16710, 25033, 33356, 41679, 50002, 58325, 65536];
865        data_sample_at_11111[76] = 70;
866        ref_cb[76] = vec![8224, 16588, 24952, 33316, 41680, 50044, 58408, 65536];
867        data_sample_at_11111[77] = 228;
868        ref_cb[77] = vec![8264, 16464, 24664, 32864, 41064, 49264, 57464, 65536];
869        data_sample_at_11111[78] = 0;
870        ref_cb[78] = vec![8304, 16544, 24784, 33024, 41264, 49504, 57744, 65536];
871        data_sample_at_11111[79] = 0;
872        ref_cb[79] = vec![8344, 16624, 24904, 33184, 41464, 49744, 58024, 65536];
873        data_sample_at_11111[80] = 50;
874        ref_cb[80] = vec![8384, 16704, 25024, 33344, 41664, 49984, 58304, 65536];
875        data_sample_at_11111[81] = 214;
876        ref_cb[81] = vec![8215, 16575, 24935, 33295, 41655, 50015, 58375, 65536];
877        data_sample_at_11111[82] = 0;
878        ref_cb[82] = vec![8254, 16654, 25054, 33454, 41854, 50254, 58654, 65536];
879        data_sample_at_11111[83] = 0;
880        ref_cb[83] = vec![8293, 16522, 24751, 32980, 41209, 49438, 57667, 65536];
881        data_sample_at_11111[84] = 50;
882        ref_cb[84] = vec![8332, 16600, 24868, 33136, 41404, 49672, 57940, 65536];
883        data_sample_at_11111[85] = 69;
884        ref_cb[85] = vec![8371, 16678, 24985, 33292, 41599, 49906, 58213, 65536];
885        data_sample_at_11111[86] = 0;
886        ref_cb[86] = vec![8196, 16542, 24888, 33234, 41580, 49926, 58272, 65536];
887        data_sample_at_11111[87] = 0;
888        ref_cb[87] = vec![8234, 16619, 25004, 33389, 41774, 50159, 58544, 65536];
889        data_sample_at_11111[88] = 70;
890        ref_cb[88] = vec![8272, 16480, 24688, 32896, 41104, 49312, 57520, 65536];
891        data_sample_at_11111[89] = 136;
892        ref_cb[89] = vec![8310, 16556, 24802, 33048, 41294, 49540, 57786, 65536];
893        data_sample_at_11111[90] = 0;
894        ref_cb[90] = vec![8348, 16632, 24916, 33200, 41484, 49768, 58052, 65536];
895        data_sample_at_11111[91] = 0;
896        ref_cb[91] = vec![8386, 16708, 25030, 33352, 41674, 49996, 58318, 65536];
897        data_sample_at_11111[92] = 101;
898        ref_cb[92] = vec![8204, 16564, 24924, 33284, 41644, 50004, 58364, 65536];
899        data_sample_at_11111[93] = 36;
900        ref_cb[93] = vec![8241, 16639, 25037, 33435, 41833, 50231, 58629, 65536];
901        data_sample_at_11111[94] = 196;
902        ref_cb[94] = vec![8278, 16492, 24706, 32920, 41134, 49348, 57562, 65536];
903        data_sample_at_11111[95] = 0;
904        ref_cb[95] = vec![8315, 16566, 24817, 33068, 41319, 49570, 57821, 65536];
905        data_sample_at_11111[96] = 0;
906        ref_cb[96] = vec![8352, 16640, 24928, 33216, 41504, 49792, 58080, 65536];
907        data_sample_at_11111[97] = 24;
908        ref_cb[97] = vec![8389, 16714, 25039, 33364, 41689, 50014, 58339, 65536];
909        data_sample_at_11111[98] = 8;
910        ref_cb[98] = vec![8200, 16562, 24924, 33286, 41648, 50010, 58372, 65536];
911        data_sample_at_11111[99] = 0;
912        ref_cb[99] = vec![8236, 16635, 25034, 33433, 41832, 50231, 58630, 65536];
913        data_sample_at_11111[100] = 0;
914        ref_cb[100] = vec![8272, 16480, 24688, 32896, 41104, 49312, 57520, 65536];
915        data_sample_at_11111[101] = 125;
916        ref_cb[101] = vec![8308, 16552, 24796, 33040, 41284, 49528, 57772, 65536];
917        data_sample_at_11111[102] = 173;
918        ref_cb[102] = vec![8344, 16624, 24904, 33184, 41464, 49744, 58024, 65536];
919        data_sample_at_11111[103] = 126;
920        ref_cb[103] = vec![8380, 16696, 25012, 33328, 41644, 49960, 58276, 65536];
921        data_sample_at_11111[104] = 0;
922        ref_cb[104] = vec![8416, 16768, 25120, 33472, 41824, 50176, 58528, 65536];
923        data_sample_at_11111[105] = 0;
924        ref_cb[105] = vec![8219, 16607, 24995, 33383, 41771, 50159, 58547, 65536];
925        data_sample_at_11111[106] = 159;
926        ref_cb[106] = vec![8254, 16678, 25102, 33526, 41950, 50374, 58798, 65536];
927        data_sample_at_11111[107] = 210;
928        ref_cb[107] = vec![8289, 16514, 24739, 32964, 41189, 49414, 57639, 65536];
929        data_sample_at_11111[108] = 178;
930        ref_cb[108] = vec![8324, 16584, 24844, 33104, 41364, 49624, 57884, 65536];
931        data_sample_at_11111[109] = 0;
932        ref_cb[109] = vec![8359, 16654, 24949, 33244, 41539, 49834, 58129, 65536];
933        data_sample_at_11111[110] = 0;
934        ref_cb[110] = vec![8394, 16724, 25054, 33384, 41714, 50044, 58374, 65536];
935        data_sample_at_11111[111] = 170;
936        ref_cb[111] = vec![8429, 16794, 25159, 33524, 41889, 50254, 58619, 65536];
937        data_sample_at_11111[112] = 173;
938        ref_cb[112] = vec![8224, 16624, 25024, 33424, 41824, 50224, 58624, 65536];
939        data_sample_at_11111[113] = 235;
940        ref_cb[113] = vec![8258, 16452, 24646, 32840, 41034, 49228, 57422, 65536];
941        data_sample_at_11111[114] = 0;
942        ref_cb[114] = vec![8292, 16520, 24748, 32976, 41204, 49432, 57660, 65536];
943        data_sample_at_11111[115] = 0;
944        ref_cb[115] = vec![8326, 16588, 24850, 33112, 41374, 49636, 57898, 65536];
945        data_sample_at_11111[116] = 0;
946        ref_cb[116] = vec![8360, 16656, 24952, 33248, 41544, 49840, 58136, 65536];
947        data_sample_at_11111[117] = 24;
948        ref_cb[117] = vec![8394, 16724, 25054, 33384, 41714, 50044, 58374, 65536];
949        data_sample_at_11111[118] = 228;
950        ref_cb[118] = vec![8428, 16792, 25156, 33520, 41884, 50248, 58612, 65536];
951        data_sample_at_11111[119] = 0;
952        ref_cb[119] = vec![8215, 16613, 25011, 33409, 41807, 50205, 58603, 65536];
953        data_sample_at_11111[120] = 0;
954        ref_cb[120] = vec![8248, 16680, 25112, 33544, 41976, 50408, 58840, 65536];
955        data_sample_at_11111[121] = 0;
956        ref_cb[121] = vec![8281, 16498, 24715, 32932, 41149, 49366, 57583, 65536];
957        data_sample_at_11111[122] = 101;
958        ref_cb[122] = vec![8314, 16564, 24814, 33064, 41314, 49564, 57814, 65536];
959        data_sample_at_11111[123] = 174;
960        ref_cb[123] = vec![8347, 16630, 24913, 33196, 41479, 49762, 58045, 65536];
961        data_sample_at_11111[124] = 126;
962        ref_cb[124] = vec![8380, 16696, 25012, 33328, 41644, 49960, 58276, 65536];
963        data_sample_at_11111[125] = 0;
964        ref_cb[125] = vec![8413, 16762, 25111, 33460, 41809, 50158, 58507, 65536];
965        data_sample_at_11111[126] = 0;
966        ref_cb[126] = vec![8192, 16574, 24956, 33338, 41720, 50102, 58484, 65536];
967        data_sample_at_11111[127] = 0;
968        ref_cb[127] = vec![8224, 16639, 25054, 33469, 41884, 50299, 58714, 65536];
969
970        // Now run the loop with this reference data.
971        for i in 0..128 {
972            let data = get_triggering_base_data(65536, i);
973
974            // This check is here so that the tests written against this chunker
975            // can verify that the test data input is correct.
976            assert_eq!(data[11111], data_sample_at_11111[i]);
977
978            // Uncomment to create the line above.
979            // eprintln!("data_sample_at_11111[{i}]={};", data[11111]);
980
981            // Now, run the chunks through the default chunker.
982            let chunks = ChunkerTestWrapper::default().next_block(&data, true);
983
984            // Get the boundaries indices as determined by the size of the chunks above.
985            let chunk_boundaries: Vec<usize> = get_chunk_boundaries(&chunks);
986
987            // Uncomment this to generate the table above.
988            // eprintln!("ref_cb[{i}]=vec!{chunk_boundaries:?};");
989
990            assert_eq!(chunk_boundaries, ref_cb[i]);
991        }
992
993        // eprintln!("assert_eq!(chunk_boundaries, vec!{chunk_boundaries:?})");
994        // assert_eq!(chunk_boundaries, vec![131072, 262144, 393216, 524288, 655360, 786432, 917504, 1000000])
995    }
996}