1use fastcdc::v2020::FastCDC;
2
3use crate::error::{Error, Result};
4
5pub const DEFAULT_MIN: usize = 4 * 1024;
6pub const DEFAULT_AVG: usize = 64 * 1024;
7pub const DEFAULT_MAX: usize = 4 * 1024 * 1024;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Chunk {
11 pub offset: usize,
12 pub length: usize,
13}
14
15#[derive(Debug, Clone, Copy)]
16pub struct FastCdcSplitter {
17 min_size: usize,
18 avg_size: usize,
19 max_size: usize,
20}
21
22impl FastCdcSplitter {
23 pub fn new(min_size: usize, avg_size: usize, max_size: usize) -> Result<Self> {
24 if min_size == 0 || min_size > avg_size || avg_size > max_size {
25 return Err(Error::InvalidCdcParams);
26 }
27 Ok(Self {
28 min_size,
29 avg_size,
30 max_size,
31 })
32 }
33
34 pub fn v1_defaults() -> Self {
35 Self::new(DEFAULT_MIN, DEFAULT_AVG, DEFAULT_MAX).expect("defaults are valid")
36 }
37
38 pub fn split_bytes(&self, data: &[u8]) -> Vec<Chunk> {
39 FastCDC::new(data, self.min_size, self.avg_size, self.max_size)
40 .map(|c| Chunk {
41 offset: c.offset,
42 length: c.length,
43 })
44 .collect()
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use rand::Rng;
51
52 use super::*;
53
54 #[test]
55 fn rejects_invalid_params() {
56 assert!(FastCdcSplitter::new(0, DEFAULT_AVG, DEFAULT_MAX).is_err());
57 assert!(FastCdcSplitter::new(DEFAULT_AVG, DEFAULT_MIN, DEFAULT_MAX).is_err());
58 }
59
60 #[test]
61 fn splits_and_respects_bounds() {
62 let mut data = vec![0u8; DEFAULT_MAX * 2 + DEFAULT_AVG];
63 rand::rng().fill_bytes(&mut data);
64 let splitter = FastCdcSplitter::v1_defaults();
65 let chunks = splitter.split_bytes(&data);
66 assert!(!chunks.is_empty());
67
68 let mut total = 0usize;
69 for (i, c) in chunks.iter().enumerate() {
70 assert_eq!(c.offset, total);
71 total += c.length;
72 if i + 1 != chunks.len() {
73 assert!(c.length >= DEFAULT_MIN);
74 assert!(c.length <= DEFAULT_MAX);
75 } else {
76 assert!(c.length <= DEFAULT_MAX);
77 }
78 }
79 assert_eq!(total, data.len());
80 }
81
82 #[test]
83 fn small_input_returns_single_chunk() {
84 let data = vec![1u8; 1024];
85 let splitter = FastCdcSplitter::v1_defaults();
86 let chunks = splitter.split_bytes(&data);
87 assert_eq!(chunks.len(), 1);
88 assert_eq!(chunks[0].length, data.len());
89 assert_eq!(chunks[0].offset, 0);
90 }
91}