1pub const DOWNLOAD_CHUNK_MAX: usize = 800 * 1024;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ChunkSpan {
16 pub offset: u64,
18 pub len: usize,
20 pub last: bool,
22}
23
24pub fn chunk_plan(total: u64, max: usize) -> impl Iterator<Item = ChunkSpan> {
29 let step = u64::try_from(max.max(1)).unwrap_or(u64::MAX);
30 let mut offset = 0u64;
31 let mut done = false;
32 std::iter::from_fn(move || {
33 if done {
34 return None;
35 }
36 let len = (total - offset).min(step);
37 offset += len;
38 done = offset == total;
39 Some(ChunkSpan {
40 offset: offset - len,
41 len: usize::try_from(len).unwrap_or(usize::MAX),
43 last: done,
44 })
45 })
46}
47
48#[cfg(test)]
49mod tests {
50 use proptest::prelude::*;
51
52 use super::*;
53
54 fn plan(total: u64, max: usize) -> Vec<ChunkSpan> {
55 chunk_plan(total, max).collect()
56 }
57
58 fn span(offset: u64, len: usize, last: bool) -> ChunkSpan {
59 ChunkSpan { offset, len, last }
60 }
61
62 #[test]
63 fn chunk_plan_empty_is_single_last() {
64 assert_eq!(plan(0, DOWNLOAD_CHUNK_MAX), [span(0, 0, true)]);
65 }
66
67 #[test]
68 fn chunk_plan_exact_multiple() {
69 let max = DOWNLOAD_CHUNK_MAX;
70 let total = 2 * max as u64;
71 assert_eq!(
72 plan(total, max),
73 [span(0, max, false), span(max as u64, max, true)]
74 );
75 assert_eq!(plan(4, 4), [span(0, 4, true)]);
76 }
77
78 #[test]
79 fn chunk_plan_remainder() {
80 assert_eq!(
81 plan(10, 4),
82 [span(0, 4, false), span(4, 4, false), span(8, 2, true)]
83 );
84 assert_eq!(
85 plan(3, 0),
86 [span(0, 1, false), span(1, 1, false), span(2, 1, true)]
87 );
88 }
89
90 proptest! {
91 #[test]
92 fn chunk_plan_spans_are_contiguous(total in 0u64..100_000, max in 1usize..5_000) {
93 let spans = plan(total, max);
94 let mut next = 0u64;
95 for (i, s) in spans.iter().enumerate() {
96 prop_assert_eq!(s.offset, next);
97 prop_assert!(s.len <= max);
98 prop_assert_eq!(s.last, i + 1 == spans.len());
99 if total > 0 {
100 prop_assert!(s.len > 0);
101 }
102 next += s.len as u64;
103 }
104 prop_assert_eq!(next, total);
105 }
106 }
107}