Skip to main content

assetpack_core/
cdc.rs

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}