1use crate::{
2 ChunkHeader, ChunkType, FileHeader, CHUNK_HEADER_BYTES_LEN, DEFAULT_BLOCKSIZE,
3 FILE_HEADER_BYTES_LEN,
4};
5use thiserror::Error;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct SplitChunk {
12 pub header: ChunkHeader,
14 pub offset: usize,
16 pub size: usize,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Split {
24 pub header: FileHeader,
26 pub chunks: Vec<SplitChunk>,
28}
29
30impl Split {
31 fn from_chunks(chunks: Vec<SplitChunk>, block_size: u32) -> Self {
32 let n_chunks = chunks.len() as u32;
33 let blocks = chunks.iter().map(|c| c.header.chunk_size).sum();
34
35 let header = FileHeader {
36 block_size,
37 blocks,
38 chunks: n_chunks,
39 checksum: 0,
40 };
41
42 Split { header, chunks }
43 }
44
45 pub fn sparse_size(&self) -> usize {
47 FILE_HEADER_BYTES_LEN
48 + self
49 .chunks
50 .iter()
51 .map(|c| c.header.total_size as usize)
52 .sum::<usize>()
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
57struct SplitBuilder {
58 space: u32,
59 block_size: u32,
60 chunks: Vec<SplitChunk>,
61}
62
63impl SplitBuilder {
64 fn new(block_size: u32, mut space: u32, blocks_offset: u32) -> Self {
65 space -= FILE_HEADER_BYTES_LEN as u32;
66 let chunks = if blocks_offset == 0 {
67 vec![]
68 } else {
69 let header = ChunkHeader::new_dontcare(blocks_offset);
71 space -= header.total_size;
72 vec![SplitChunk {
73 header,
74 offset: 0,
75 size: 0,
76 }]
77 };
78 Self {
79 space,
80 block_size,
81 chunks,
82 }
83 }
84
85 fn try_add_chunk(&mut self, chunk: &ChunkHeader, image_offset: usize) -> bool {
86 if self.space > chunk.total_size {
87 let split = SplitChunk {
88 header: chunk.clone(),
89 offset: image_offset,
90 size: chunk.data_size(),
91 };
92 self.chunks.push(split);
93 self.space -= chunk.total_size;
94 true
95 } else {
96 false
97 }
98 }
99
100 fn add_raw(&mut self, image_offset: usize, blocks: u32) -> u32 {
102 let left = self.space.saturating_sub(CHUNK_HEADER_BYTES_LEN as u32);
103 let blocks_left = left / self.block_size;
104
105 if blocks_left > 0 {
106 let blocks = blocks.min(blocks_left);
107 let header = ChunkHeader::new_raw(blocks, self.block_size);
108 self.space -= header.total_size;
109
110 self.chunks.push(SplitChunk {
111 size: header.data_size(),
112 offset: image_offset,
113 header,
114 });
115
116 blocks
117 } else {
118 0
119 }
120 }
121
122 fn finish(self) -> Split {
123 Split::from_chunks(self.chunks, self.block_size)
124 }
125}
126
127#[derive(Debug, Error)]
128pub enum SplitError {
129 #[error("Size is too small to fit chunks")]
130 TooSmall,
131}
132
133fn check_minimal_size(size: u32, block_size: u32) -> Result<(), SplitError> {
134 if size < FILE_HEADER_BYTES_LEN as u32 + 2 * CHUNK_HEADER_BYTES_LEN as u32 + block_size {
139 return Err(SplitError::TooSmall);
140 }
141 Ok(())
142}
143
144pub fn split_image(
147 header: &FileHeader,
148 chunks: &[ChunkHeader],
149 size: u32,
150) -> Result<Vec<Split>, SplitError> {
151 check_minimal_size(size, header.block_size)?;
152 let (_, _, builder, mut splits) = chunks.iter().try_fold(
153 (
154 0,
156 FILE_HEADER_BYTES_LEN + CHUNK_HEADER_BYTES_LEN,
158 SplitBuilder::new(header.block_size, size, 0),
159 vec![],
161 ),
162 |(block_offset, image_offset, mut builder, mut splits), chunk| {
163 if !builder.try_add_chunk(chunk, image_offset) {
164 if chunk.chunk_type == ChunkType::Raw {
165 let mut blocks = 0;
167 loop {
168 blocks += builder.add_raw(
169 image_offset + (blocks * header.block_size) as usize,
170 chunk.chunk_size - blocks,
171 );
172
173 if blocks >= chunk.chunk_size {
174 break;
175 } else {
176 splits.push(builder.finish());
177 builder =
178 SplitBuilder::new(header.block_size, size, block_offset + blocks);
179 }
180 }
181 } else {
182 splits.push(builder.finish());
183 builder = SplitBuilder::new(header.block_size, size, block_offset);
184 if !builder.try_add_chunk(chunk, image_offset) {
185 return Err(SplitError::TooSmall);
186 }
187 }
188 }
189 Ok((
190 block_offset + chunk.chunk_size,
191 image_offset + chunk.total_size as usize,
192 builder,
193 splits,
194 ))
195 },
196 )?;
197 splits.push(builder.finish());
198 Ok(splits)
199}
200
201pub fn split_raw(raw_size: usize, size: u32) -> Result<Vec<Split>, SplitError> {
205 check_minimal_size(size, DEFAULT_BLOCKSIZE)?;
206 let raw_blocks = raw_size.div_ceil(DEFAULT_BLOCKSIZE as usize) as u32;
207
208 let mut block_offset = 0;
209 let mut splits = vec![];
210
211 while raw_blocks > block_offset {
212 let mut builder = SplitBuilder::new(DEFAULT_BLOCKSIZE, size, block_offset);
213 block_offset += builder.add_raw(
214 (block_offset * DEFAULT_BLOCKSIZE) as usize,
215 raw_blocks - block_offset,
216 );
217 splits.push(builder.finish());
218 }
219 Ok(splits)
220}
221
222#[cfg(test)]
223mod test {
224 use super::*;
225
226 #[test]
227 fn split_simple() {
228 let header = FileHeader {
229 block_size: 4096,
230 blocks: 1024,
231 chunks: 2,
232 checksum: 0,
233 };
234 let chunks = [
235 ChunkHeader::new_fill(8),
236 ChunkHeader::new_raw(1024 - 8, 4096),
237 ];
238
239 let split = split_image(&header, &chunks, 1024 * 4096).unwrap();
240 assert_eq!(split.len(), 1);
241 let split = &split[0];
242
243 assert_eq!(split.header, header);
244 assert_eq!(split.chunks.len(), 2);
245 assert_eq!(
246 &split.chunks[0],
247 &SplitChunk {
248 header: chunks[0].clone(),
249 offset: FILE_HEADER_BYTES_LEN + CHUNK_HEADER_BYTES_LEN,
250 size: chunks[0].data_size()
251 }
252 );
253 assert_eq!(
254 &split.chunks[1],
255 &SplitChunk {
256 header: chunks[1].clone(),
257 offset: FILE_HEADER_BYTES_LEN + 2 * CHUNK_HEADER_BYTES_LEN + 4,
258 size: chunks[1].data_size(),
259 }
260 );
261 }
262
263 #[test]
264 fn split_multiple() {
265 let header = FileHeader {
266 block_size: 4096,
267 blocks: 2048,
268 chunks: 2,
269 checksum: 0,
270 };
271 let chunks = [
272 ChunkHeader::new_fill(8),
273 ChunkHeader::new_raw(1024 - 8, 4096),
274 ChunkHeader::new_raw(1024 - 8, 4096),
275 ChunkHeader::new_fill(8),
276 ];
277 let expected = [
278 Split {
279 header: FileHeader {
280 block_size: 4096,
281 blocks: 519,
282 chunks: 2,
283 checksum: 0,
284 },
285 chunks: vec![
286 SplitChunk {
287 header: ChunkHeader::new_fill(8),
288 offset: FILE_HEADER_BYTES_LEN + CHUNK_HEADER_BYTES_LEN,
289 size: 4,
290 },
291 SplitChunk {
292 header: ChunkHeader::new_raw(511, 4096),
293 offset: FILE_HEADER_BYTES_LEN + 2 * CHUNK_HEADER_BYTES_LEN + 4,
294 size: 511 * 4096,
295 },
296 ],
297 },
298 Split {
299 header: FileHeader {
300 block_size: 4096,
301 blocks: 519 + 511,
302 chunks: 3,
303 checksum: 0,
304 },
305 chunks: vec![
306 SplitChunk {
307 header: ChunkHeader::new_dontcare(519),
308 offset: 0,
309 size: 0,
310 },
311 SplitChunk {
313 header: ChunkHeader::new_raw(505, 4096),
314 offset: FILE_HEADER_BYTES_LEN + 2 * CHUNK_HEADER_BYTES_LEN + 4 + 511 * 4096,
315 size: 505 * 4096,
316 },
317 SplitChunk {
319 header: ChunkHeader::new_raw(6, 4096),
320 offset: FILE_HEADER_BYTES_LEN
321 + 3 * CHUNK_HEADER_BYTES_LEN
322 + 4
323 + 1016 * 4096,
324 size: 6 * 4096,
325 },
326 ],
327 },
328 Split {
329 header: FileHeader {
330 block_size: 4096,
331 blocks: 519 + 511 + 511,
332 chunks: 2,
333 checksum: 0,
334 },
335 chunks: vec![
336 SplitChunk {
337 header: ChunkHeader::new_dontcare(519 + 511),
338 offset: 0,
339 size: 0,
340 },
341 SplitChunk {
343 header: ChunkHeader::new_raw(511, 4096),
344 offset: FILE_HEADER_BYTES_LEN
345 + 3 * CHUNK_HEADER_BYTES_LEN
346 + 4
347 + 1016 * 4096
348 + 6 * 4096,
349 size: 511 * 4096,
350 },
351 ],
352 },
353 Split {
354 header: FileHeader {
355 block_size: 4096,
356 blocks: 2048,
357 chunks: 3,
358 checksum: 0,
359 },
360 chunks: vec![
361 SplitChunk {
362 header: ChunkHeader::new_dontcare(519 + 511 + 511),
363 offset: 0,
364 size: 0,
365 },
366 SplitChunk {
369 header: ChunkHeader::new_raw(499, 4096),
370 offset: FILE_HEADER_BYTES_LEN
371 + 3 * CHUNK_HEADER_BYTES_LEN
372 + 4
373 + 1016 * 4096
374 + 517 * 4096,
375 size: 499 * 4096,
376 },
377 SplitChunk {
379 header: ChunkHeader::new_fill(8),
380 offset: FILE_HEADER_BYTES_LEN
381 + 4 * CHUNK_HEADER_BYTES_LEN
382 + 4
383 + 1016 * 4096
384 + 1016 * 4096,
385 size: 4,
386 },
387 ],
388 },
389 ];
390
391 let splits = split_image(&header, &chunks, 512 * 4096).unwrap();
392 for (i, (split, expected)) in splits.iter().zip(expected.iter()).enumerate() {
393 assert_eq!(split, expected, "split {i} mismatch");
394 }
395 assert_eq!(splits.len(), expected.len());
396 }
397
398 #[test]
399 fn test_split_raw() {
400 let splits = split_raw(8 * DEFAULT_BLOCKSIZE as usize, 3 * DEFAULT_BLOCKSIZE).unwrap();
401 assert_eq!(splits.len(), 4, "Incorrect parts: {splits:?}");
402 for (i, split) in splits.iter().enumerate() {
403 assert_eq!(split.header.block_size, 4096);
404 assert_eq!(split.header.checksum, 0);
405 let raw = if i == 0 {
406 assert_eq!(split.header.chunks, 1);
407 assert_eq!(split.chunks.len(), 1);
408 &split.chunks[0]
409 } else {
410 assert_eq!(split.header.chunks, 2);
411 assert_eq!(split.chunks.len(), 2);
412 assert_eq!(
413 split.chunks[0],
414 SplitChunk {
415 header: ChunkHeader {
416 chunk_type: ChunkType::DontCare,
417 chunk_size: 2 * i as u32,
418 total_size: CHUNK_HEADER_BYTES_LEN as u32
419 },
420 offset: 0,
421 size: 0
422 },
423 "chunk {i}"
424 );
425 &split.chunks[1]
426 };
427 assert_eq!(
428 raw,
429 &SplitChunk {
430 header: ChunkHeader {
431 chunk_type: ChunkType::Raw,
432 chunk_size: 2,
433 total_size: 2 * DEFAULT_BLOCKSIZE + CHUNK_HEADER_BYTES_LEN as u32
434 },
435 offset: 2 * i * DEFAULT_BLOCKSIZE as usize,
436 size: 2 * DEFAULT_BLOCKSIZE as usize
437 },
438 "chunk {i}"
439 );
440 }
441 }
442}