1#[cfg(target_endian = "big")]
6assert!(false, "This library assumes little-endian!");
7
8pub mod builder;
9mod format;
10pub mod reader;
11
12use crate::format::{ChunkHeader, SparseHeader};
13use anyhow::{bail, ensure, Context, Result};
14use core::fmt;
15use serde::de::DeserializeOwned;
16use std::fs::File;
17use std::io::{Cursor, Read, Seek, SeekFrom, Write};
18use std::path::Path;
19use tempfile::{NamedTempFile, TempPath};
20
21const BLK_SIZE: usize = 0x1000;
24
25fn deserialize_from<'a, T: DeserializeOwned, R: Read + ?Sized>(source: &mut R) -> Result<T> {
26 let mut buf = vec![0u8; std::mem::size_of::<T>()];
27 source.read_exact(&mut buf[..]).context("Failed to read bytes")?;
28 Ok(bincode::deserialize(&buf[..])?)
29}
30
31pub trait Reader: Read + Seek {}
33
34impl<T: Read + Seek> Reader for T {}
35
36pub trait Writer: Write + Seek {
38 fn set_len(&mut self, size: u64) -> Result<()>;
40}
41
42impl Writer for File {
43 fn set_len(&mut self, size: u64) -> Result<()> {
44 Ok(File::set_len(self, size)?)
45 }
46}
47
48impl Writer for Cursor<Vec<u8>> {
49 fn set_len(&mut self, size: u64) -> Result<()> {
50 Vec::resize(self.get_mut(), size as usize, 0u8);
51 Ok(())
52 }
53}
54
55struct LimitedReader<'a>(pub &'a mut dyn Reader, pub usize);
61
62impl<'a> Read for LimitedReader<'a> {
63 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
64 let offset = self.0.stream_position()?;
65 let avail = self.1.saturating_sub(offset as usize);
66 let to_read = std::cmp::min(avail, buf.len());
67 self.0.read(&mut buf[..to_read])
68 }
69}
70
71impl<'a> Seek for LimitedReader<'a> {
72 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
73 self.0.seek(pos)
74 }
75}
76
77pub fn is_sparse_image<R: Reader>(reader: &mut R) -> bool {
79 || -> Option<bool> {
80 let header: SparseHeader = deserialize_from(reader).ok()?;
81 let is_sparse = header.magic == format::SPARSE_HEADER_MAGIC;
82 reader.seek(SeekFrom::Start(0)).ok()?;
83 Some(is_sparse)
84 }()
85 .unwrap_or(false)
86}
87
88#[derive(Clone, PartialEq, Debug)]
89enum Chunk {
90 Raw { start: u64, size: usize },
94 Fill { start: u64, size: usize, value: u32 },
98 DontCare { start: u64, size: usize },
105 #[allow(dead_code)]
109 Crc32 { checksum: u32 },
110}
111
112impl Chunk {
113 pub fn read_metadata<R: Reader>(reader: &mut R, offset: u64, block_size: u32) -> Result<Self> {
118 let header: ChunkHeader =
119 deserialize_from(reader).context("Failed to read chunk header")?;
120 ensure!(header.valid(), "Invalid chunk header");
121
122 let size = (header.chunk_sz * block_size) as usize;
123 match header.chunk_type {
124 format::CHUNK_TYPE_RAW => Ok(Self::Raw { start: offset, size }),
125 format::CHUNK_TYPE_FILL => {
126 let value: u32 =
127 deserialize_from(reader).context("Failed to deserialize fill value")?;
128 Ok(Self::Fill { start: offset, size, value })
129 }
130 format::CHUNK_TYPE_DONT_CARE => Ok(Self::DontCare { start: offset, size }),
131 format::CHUNK_TYPE_CRC32 => {
132 let checksum: u32 =
133 deserialize_from(reader).context("Failed to deserialize checksum")?;
134 Ok(Self::Crc32 { checksum })
135 }
136 _ => unreachable!(),
138 }
139 }
140
141 fn valid(&self, block_size: usize) -> bool {
142 self.output_size() % block_size == 0
143 }
144
145 fn output_offset(&self) -> Option<u64> {
148 match self {
149 Self::Raw { start, .. } => Some(*start),
150 Self::Fill { start, .. } => Some(*start),
151 Self::DontCare { start, .. } => Some(*start),
152 Self::Crc32 { .. } => None,
153 }
154 }
155
156 fn output_size(&self) -> usize {
158 match self {
159 Self::Raw { size, .. } => *size,
160 Self::Fill { size, .. } => *size,
161 Self::DontCare { size, .. } => *size,
162 Self::Crc32 { .. } => 0,
163 }
164 }
165
166 fn output_blocks(&self, block_size: usize) -> u32 {
168 let size_bytes = self.output_size();
169 ((size_bytes + block_size - 1) / block_size) as u32
170 }
171
172 fn chunk_type(&self) -> u16 {
175 match self {
176 Self::Raw { .. } => format::CHUNK_TYPE_RAW,
177 Self::Fill { .. } => format::CHUNK_TYPE_FILL,
178 Self::DontCare { .. } => format::CHUNK_TYPE_DONT_CARE,
179 Self::Crc32 { .. } => format::CHUNK_TYPE_CRC32,
180 }
181 }
182
183 fn chunk_data_len(&self) -> usize {
186 let header_size = format::CHUNK_HEADER_SIZE;
187 let data_size = match self {
188 Self::Raw { size, .. } => *size,
189 Self::Fill { .. } => std::mem::size_of::<u32>(),
190 Self::DontCare { .. } => 0,
191 Self::Crc32 { .. } => std::mem::size_of::<u32>(),
192 };
193 header_size + data_size
194 }
195
196 fn write<W: Write + Seek, R: Read + Seek>(
200 &self,
201 source: Option<&mut R>,
202 dest: &mut W,
203 ) -> Result<()> {
204 ensure!(self.valid(BLK_SIZE), "Not writing invalid chunk");
205 let header = ChunkHeader::new(
206 self.chunk_type(),
207 0x0,
208 self.output_blocks(BLK_SIZE),
209 self.chunk_data_len() as u32,
210 );
211
212 let header_bytes: Vec<u8> = bincode::serialize(&header)?;
213 std::io::copy(&mut Cursor::new(header_bytes), dest)?;
214
215 match self {
216 Self::Raw { size, .. } => {
217 ensure!(source.is_some(), "No source for Raw chunk");
218 let n = std::io::copy(source.unwrap(), dest)? as usize;
219 if n < *size {
220 let zeroes = vec![0u8; *size - n];
221 std::io::copy(&mut Cursor::new(zeroes), dest)?;
222 }
223 }
224 Self::Fill { value, .. } => {
225 bincode::serialize_into(dest, value)?;
227 }
228 Self::DontCare { .. } => {
229 }
231 Self::Crc32 { checksum } => {
232 bincode::serialize_into(dest, checksum)?;
233 }
234 }
235 Ok(())
236 }
237}
238
239impl fmt::Display for Chunk {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 let message = match self {
242 Self::Raw { start, size } => {
243 format!("RawChunk: start: {}, total bytes: {}", start, size)
244 }
245 Self::Fill { start, size, value } => {
246 format!("FillChunk: start: {}, value: {}, n_blocks: {}", start, value, size)
247 }
248 Self::DontCare { start, size } => {
249 format!("DontCareChunk: start: {}, bytes: {}", start, size)
250 }
251 Self::Crc32 { checksum } => format!("Crc32Chunk: checksum: {:?}", checksum),
252 };
253 write!(f, "{}", message)
254 }
255}
256
257#[derive(Clone, Debug, PartialEq)]
258struct SparseFileWriter {
259 chunks: Vec<Chunk>,
260}
261
262impl SparseFileWriter {
263 fn new(chunks: Vec<Chunk>) -> SparseFileWriter {
264 SparseFileWriter { chunks }
265 }
266
267 fn total_blocks(&self) -> u32 {
268 self.chunks.iter().map(|c| c.output_blocks(BLK_SIZE)).sum()
269 }
270
271 fn total_bytes(&self) -> usize {
272 self.chunks.iter().map(|c| c.output_size()).sum()
273 }
274
275 fn write<W: Write + Seek, R: Read + Seek>(&self, reader: &mut R, writer: &mut W) -> Result<()> {
276 let header = SparseHeader::new(
277 BLK_SIZE.try_into().unwrap(), self.total_blocks(), self.chunks.len().try_into().unwrap(), );
281
282 let header_bytes: Vec<u8> = bincode::serialize(&header)?;
283 std::io::copy(&mut Cursor::new(header_bytes), writer)?;
284
285 for chunk in &self.chunks {
286 let mut reader = if let &Chunk::Raw { start, size } = chunk {
287 reader.seek(SeekFrom::Start(start))?;
288 Some(LimitedReader(reader, start as usize + size))
289 } else {
290 None
291 };
292 chunk.write(reader.as_mut(), writer)?;
293 }
294
295 Ok(())
296 }
297}
298
299impl fmt::Display for SparseFileWriter {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 write!(f, r"SparseFileWriter: {} Chunks:", self.chunks.len())
302 }
303}
304
305fn add_sparse_chunk(r: &mut Vec<Chunk>, chunk: Chunk) -> Result<()> {
314 match r.last_mut() {
315 Some(last) => match (&last, &chunk) {
318 (Chunk::Raw { start, size }, Chunk::Raw { size: new_length, .. }) => {
319 *last = Chunk::Raw { start: *start, size: size + new_length };
320 return Ok(());
321 }
322 (
323 Chunk::Fill { start, size, value },
324 Chunk::Fill { size: new_size, value: new_value, .. },
325 ) if value == new_value => {
326 *last = Chunk::Fill { start: *start, size: size + new_size, value: *value };
327 return Ok(());
328 }
329 (Chunk::DontCare { start, size }, Chunk::DontCare { size: new_size, .. }) => {
330 *last = Chunk::DontCare { start: *start, size: size + new_size };
331 return Ok(());
332 }
333 _ => {}
334 },
335 None => {}
336 }
337
338 r.push(chunk);
343 Ok(())
344}
345
346pub fn unsparse<W: Writer, R: Reader>(source: &mut R, dest: &mut W) -> Result<()> {
348 let header: SparseHeader = deserialize_from(source).context("Failed to read header")?;
349 ensure!(header.valid(), "Invalid sparse image header {:?}", header);
350 let num_chunks = header.total_chunks as usize;
351
352 for _ in 0..num_chunks {
353 expand_chunk(source, dest, header.blk_sz).context("Failed to expand chunk")?;
354 }
355 let offset = dest.stream_position()?;
357 dest.set_len(offset).context("Failed to truncate output")?;
358 dest.flush()?;
359 Ok(())
360}
361
362fn expand_chunk<R: Read + Seek, W: Write + Seek>(
364 source: &mut R,
365 dest: &mut W,
366 block_size: u32,
367) -> Result<()> {
368 let header: ChunkHeader =
369 deserialize_from(source).context("Failed to deserialize chunk header")?;
370 ensure!(header.valid(), "Invalid chunk header {:x?}", header);
371 let size = (header.chunk_sz * block_size) as usize;
372 match header.chunk_type {
373 format::CHUNK_TYPE_RAW => {
374 let limit = source.stream_position()? as usize + size;
375 std::io::copy(&mut LimitedReader(source, limit), dest)
376 .context("Failed to copy contents")?;
377 }
378 format::CHUNK_TYPE_FILL => {
379 let value: [u8; 4] =
380 deserialize_from(source).context("Failed to deserialize fill value")?;
381 assert!(size % 4 == 0);
382 let repeated = value.repeat(size / 4);
383 std::io::copy(&mut Cursor::new(repeated), dest).context("Failed to fill contents")?;
384 }
385 format::CHUNK_TYPE_DONT_CARE => {
386 dest.seek(SeekFrom::Current(size as i64)).context("Failed to skip contents")?;
387 }
388 format::CHUNK_TYPE_CRC32 => {
389 let _: u32 = deserialize_from(source).context("Failed to deserialize fill value")?;
390 }
391 _ => bail!("Invalid type {}", header.chunk_type),
392 };
393 Ok(())
394}
395
396fn resparse(
402 sparse_file: SparseFileWriter,
403 max_download_size: u64,
404) -> Result<Vec<SparseFileWriter>> {
405 if max_download_size as usize <= BLK_SIZE {
406 anyhow::bail!(
407 "Given maximum download size ({}) is less than the block size ({})",
408 max_download_size,
409 BLK_SIZE
410 );
411 }
412 let mut ret = Vec::<SparseFileWriter>::new();
413
414 let sunk_file_length = format::SPARSE_HEADER_SIZE
417 + (Chunk::DontCare { start: 0, size: BLK_SIZE }.chunk_data_len()
418 + Chunk::Crc32 { checksum: 2345 }.chunk_data_len());
419
420 let mut chunk_pos = 0;
421 let mut output_offset = 0;
422 while chunk_pos < sparse_file.chunks.len() {
423 log::trace!("Starting a new file at chunk position: {}", chunk_pos);
424
425 let mut file_len = 0;
426 file_len += sunk_file_length;
427
428 let mut chunks = Vec::<Chunk>::new();
429 if chunk_pos > 0 {
430 log::trace!("Adding a DontCare chunk offset: {}", chunk_pos);
433 let dont_care = Chunk::DontCare { start: 0, size: output_offset };
434 chunks.push(dont_care);
435 }
436
437 loop {
438 match sparse_file.chunks.get(chunk_pos) {
439 Some(chunk) => {
440 let curr_chunk_data_len = chunk.chunk_data_len();
441 if (file_len + curr_chunk_data_len) as u64 > max_download_size {
442 log::trace!("Current file size is: {} and adding another chunk of len: {} would put us over our max: {}", file_len, curr_chunk_data_len, max_download_size);
443
444 let remainder_size = sparse_file.total_bytes() - output_offset;
450 let dont_care =
451 Chunk::DontCare { start: output_offset as u64, size: remainder_size };
452 chunks.push(dont_care);
453 break;
454 }
455 log::trace!("chunk: {} curr_chunk_data_len: {} current file size: {} max_download_size: {} diff: {}", chunk_pos, curr_chunk_data_len, file_len, max_download_size, (max_download_size as usize - file_len - curr_chunk_data_len) );
456 add_sparse_chunk(&mut chunks, chunk.clone())?;
457 file_len += curr_chunk_data_len;
458 chunk_pos = chunk_pos + 1;
459 output_offset += chunk.output_size();
460 }
461 None => {
462 log::trace!("Finished iterating chunks");
463 break;
464 }
465 }
466 }
467 let resparsed = SparseFileWriter::new(chunks);
468 log::trace!("resparse: Adding new SparseFile: {}", resparsed);
469 ret.push(resparsed);
470 }
471
472 Ok(ret)
473}
474
475pub fn build_sparse_files(
486 name: &str,
487 file_to_upload: &str,
488 dir: &Path,
489 max_download_size: u64,
490) -> Result<Vec<TempPath>> {
491 if max_download_size as usize <= BLK_SIZE {
492 anyhow::bail!(
493 "Given maximum download size ({}) is less than the block size ({})",
494 max_download_size,
495 BLK_SIZE
496 );
497 }
498 log::debug!("Building sparse files for: {}. File: {}", name, file_to_upload);
499 let mut in_file = File::open(file_to_upload)?;
500
501 let mut total_read: usize = 0;
502 let mut chunks =
504 Vec::<Chunk>::with_capacity((in_file.metadata()?.len() as usize / BLK_SIZE) + 1);
505 let mut buf = [0u8; BLK_SIZE];
506 loop {
507 let read = in_file.read(&mut buf)?;
508 if read == 0 {
509 break;
510 }
511
512 let is_fill = buf.chunks(4).collect::<Vec<&[u8]>>().windows(2).all(|w| w[0] == w[1]);
513 if is_fill {
514 let value: u32 = bincode::deserialize(&buf[0..4])?;
520 let fill = Chunk::Fill { start: total_read as u64, size: buf.len(), value };
522 log::trace!("Sparsing file: {}. Created: {}", file_to_upload, fill);
523 chunks.push(fill);
524 } else {
525 let raw = Chunk::Raw { start: total_read as u64, size: buf.len() };
527 log::trace!("Sparsing file: {}. Created: {}", file_to_upload, raw);
528 chunks.push(raw);
529 }
530 total_read += read;
531 }
532
533 log::trace!("Creating sparse file from: {} chunks", chunks.len());
534
535 let sparse_file = SparseFileWriter::new(chunks);
545 log::trace!("Created sparse file: {}", sparse_file);
546
547 let mut ret = Vec::<TempPath>::new();
548 log::trace!("Resparsing sparse file");
549 for re_sparsed_file in resparse(sparse_file, max_download_size)? {
550 let (file, temp_path) = NamedTempFile::new_in(dir)?.into_parts();
551 let mut file_create = File::from(file);
552
553 log::trace!("Writing resparsed {} to disk", re_sparsed_file);
554 re_sparsed_file.write(&mut in_file, &mut file_create)?;
555
556 ret.push(temp_path);
557 }
558
559 log::debug!("Finished building sparse files");
560
561 Ok(ret)
562}
563
564#[cfg(test)]
568mod test {
569 use super::builder::{DataSource, SparseImageBuilder};
570 use super::{add_sparse_chunk, resparse, unsparse, Chunk, SparseFileWriter, BLK_SIZE};
571 use rand::rngs::SmallRng;
572 use rand::{RngCore, SeedableRng};
573 use std::io::{Cursor, Read as _, Seek as _, SeekFrom, Write as _};
574 use tempfile::{NamedTempFile, TempDir};
575
576 #[test]
577 fn test_fill_into_bytes() {
578 let mut dest = Cursor::new(Vec::<u8>::new());
579
580 let fill_chunk = Chunk::Fill { start: 0, size: 5 * BLK_SIZE, value: 365 };
581 fill_chunk.write(None::<&mut Cursor<Vec<u8>>>, &mut dest).unwrap();
583 assert_eq!(dest.into_inner(), [194, 202, 0, 0, 5, 0, 0, 0, 16, 0, 0, 0, 109, 1, 0, 0]);
584 }
585
586 #[test]
587 fn test_raw_into_bytes() {
588 const EXPECTED_RAW_BYTES: [u8; 22] =
589 [193, 202, 0, 0, 1, 0, 0, 0, 12, 16, 0, 0, 49, 50, 51, 52, 53, 0, 0, 0, 0, 0];
590
591 let mut source = Cursor::new(Vec::<u8>::from(&b"12345"[..]));
592 let mut sparse = Cursor::new(Vec::<u8>::new());
593 let chunk = Chunk::Raw { start: 0, size: BLK_SIZE };
594
595 chunk.write(Some(&mut source), &mut sparse).unwrap();
596 let buf = sparse.into_inner();
597 assert_eq!(buf.len(), 4108);
598 assert_eq!(&buf[..EXPECTED_RAW_BYTES.len()], EXPECTED_RAW_BYTES);
599 assert_eq!(&buf[EXPECTED_RAW_BYTES.len()..], &[0u8; 4108 - EXPECTED_RAW_BYTES.len()]);
600 }
601
602 #[test]
603 fn test_dont_care_into_bytes() {
604 let mut dest = Cursor::new(Vec::<u8>::new());
605 let chunk = Chunk::DontCare { start: 0, size: 5 * BLK_SIZE };
606
607 chunk.write(None::<&mut Cursor<Vec<u8>>>, &mut dest).unwrap();
609 assert_eq!(dest.into_inner(), [195, 202, 0, 0, 5, 0, 0, 0, 12, 0, 0, 0]);
610 }
611
612 #[test]
613 fn test_sparse_file_into_bytes() {
614 let mut source = Cursor::new(Vec::<u8>::from(&b"123"[..]));
615 let mut sparse = Cursor::new(Vec::<u8>::new());
616 let mut chunks = Vec::<Chunk>::new();
617 let fill = Chunk::Fill { start: 0, size: 4096, value: 5 };
619 chunks.push(fill);
620 let raw = Chunk::Raw { start: 0, size: 12288 };
622 chunks.push(raw);
623 let dontcare = Chunk::DontCare { start: 0, size: 4096 };
625 chunks.push(dontcare);
626
627 let sparsefile = SparseFileWriter::new(chunks);
628 sparsefile.write(&mut source, &mut sparse).unwrap();
629
630 sparse.seek(SeekFrom::Start(0)).unwrap();
631 let mut unsparsed = Cursor::new(Vec::<u8>::new());
632 unsparse(&mut sparse, &mut unsparsed).unwrap();
633 let buf = unsparsed.into_inner();
634 assert_eq!(buf.len(), 4096 + 12288 + 4096);
635 {
636 let chunks = buf[..4096].chunks(4);
637 for chunk in chunks {
638 assert_eq!(chunk, &[5u8, 0, 0, 0]);
639 }
640 }
641 assert_eq!(&buf[4096..4099], b"123");
642 assert_eq!(&buf[4099..16384], &[0u8; 12285]);
643 assert_eq!(&buf[16384..], &[0u8; 4096]);
644 }
645
646 #[test]
650 fn test_resparse_bails_on_too_small_size() {
651 let sparse = SparseFileWriter::new(Vec::<Chunk>::new());
652 assert!(resparse(sparse, 4095).is_err());
653 }
654
655 #[test]
656 fn test_resparse_splits() {
657 let max_download_size = 4096 * 2;
658
659 let mut chunks = Vec::<Chunk>::new();
660 chunks.push(Chunk::Raw { start: 0, size: 4096 });
661 chunks.push(Chunk::Fill { start: 4096, size: 4096, value: 2 });
662 chunks.push(Chunk::Raw { start: 8192, size: 4096 });
665
666 let input_sparse_file = SparseFileWriter::new(chunks);
667 let resparsed_files = resparse(input_sparse_file, max_download_size).unwrap();
668 assert_eq!(2, resparsed_files.len());
669
670 assert_eq!(3, resparsed_files[0].chunks.len());
671 assert_eq!(Chunk::Raw { start: 0, size: 4096 }, resparsed_files[0].chunks[0]);
672 assert_eq!(Chunk::Fill { start: 4096, size: 4096, value: 2 }, resparsed_files[0].chunks[1]);
673 assert_eq!(Chunk::DontCare { start: 8192, size: 4096 }, resparsed_files[0].chunks[2]);
674
675 assert_eq!(2, resparsed_files[1].chunks.len());
676 assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, resparsed_files[1].chunks[0]);
677 assert_eq!(Chunk::Raw { start: 8192, size: 4096 }, resparsed_files[1].chunks[1]);
678 }
679
680 #[test]
684 fn test_add_sparse_chunk_adds_empty() {
685 let init_vec = Vec::<Chunk>::new();
686 let mut res = init_vec.clone();
687 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
688 assert_eq!(0, init_vec.len());
689 assert_ne!(init_vec, res);
690 assert_eq!(Chunk::Fill { start: 0, size: 4096, value: 1 }, res[0]);
691 }
692
693 #[test]
694 fn test_add_sparse_chunk_fill() {
695 {
697 let mut init_vec = Vec::<Chunk>::new();
698 init_vec.push(Chunk::Fill { start: 0, size: 8192, value: 1 });
699 let mut res = init_vec.clone();
700 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 8192, value: 1 }).unwrap();
701 assert_eq!(1, res.len());
702 assert_eq!(Chunk::Fill { start: 0, size: 16384, value: 1 }, res[0]);
703 }
704
705 {
707 let mut init_vec = Vec::<Chunk>::new();
708 init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 1 });
709 let mut res = init_vec.clone();
710 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 2 }).unwrap();
711 assert_ne!(res, init_vec);
712 assert_eq!(2, res.len());
713 assert_eq!(
714 res,
715 [
716 Chunk::Fill { start: 0, size: 4096, value: 1 },
717 Chunk::Fill { start: 0, size: 4096, value: 2 }
718 ]
719 );
720 }
721
722 {
724 let mut init_vec = Vec::<Chunk>::new();
725 init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 2 });
726 let mut res = init_vec.clone();
727 add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 }).unwrap();
728 assert_ne!(res, init_vec);
729 assert_eq!(2, res.len());
730 assert_eq!(
731 res,
732 [
733 Chunk::Fill { start: 0, size: 4096, value: 2 },
734 Chunk::DontCare { start: 0, size: 4096 }
735 ]
736 );
737 }
738 }
739
740 #[test]
741 fn test_add_sparse_chunk_dont_care() {
742 {
744 let mut init_vec = Vec::<Chunk>::new();
745 init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
746 let mut res = init_vec.clone();
747 add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 }).unwrap();
748 assert_eq!(1, res.len());
749 assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, res[0]);
750 }
751
752 {
754 let mut init_vec = Vec::<Chunk>::new();
755 init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
756 let mut res = init_vec.clone();
757 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
758 assert_eq!(2, res.len());
759 assert_eq!(
760 res,
761 [
762 Chunk::DontCare { start: 0, size: 4096 },
763 Chunk::Fill { start: 0, size: 4096, value: 1 }
764 ]
765 );
766 }
767 }
768
769 #[test]
770 fn test_add_sparse_chunk_raw() {
771 {
773 let mut init_vec = Vec::<Chunk>::new();
774 init_vec.push(Chunk::Raw { start: 0, size: 12288 });
775 let mut res = init_vec.clone();
776 add_sparse_chunk(&mut res, Chunk::Raw { start: 0, size: 16384 }).unwrap();
777 assert_eq!(1, res.len());
778 assert_eq!(Chunk::Raw { start: 0, size: 28672 }, res[0]);
779 }
780
781 {
783 let mut init_vec = Vec::<Chunk>::new();
784 init_vec.push(Chunk::Raw { start: 0, size: 12288 });
785 let mut res = init_vec.clone();
786 add_sparse_chunk(&mut res, Chunk::Fill { start: 3, size: 8192, value: 1 }).unwrap();
787 assert_eq!(2, res.len());
788 assert_eq!(
789 res,
790 [
791 Chunk::Raw { start: 0, size: 12288 },
792 Chunk::Fill { start: 3, size: 8192, value: 1 }
793 ]
794 );
795 }
796 }
797
798 #[test]
799 fn test_add_sparse_chunk_crc32() {
800 {
802 let mut init_vec = Vec::<Chunk>::new();
803 init_vec.push(Chunk::Crc32 { checksum: 1234 });
804 let mut res = init_vec.clone();
805 add_sparse_chunk(&mut res, Chunk::Crc32 { checksum: 2345 }).unwrap();
806 assert_eq!(2, res.len());
807 assert_eq!(res, [Chunk::Crc32 { checksum: 1234 }, Chunk::Crc32 { checksum: 2345 }]);
808 }
809
810 {
812 let mut init_vec = Vec::<Chunk>::new();
813 init_vec.push(Chunk::Crc32 { checksum: 1234 });
814 let mut res = init_vec.clone();
815 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
816 assert_eq!(2, res.len());
817 assert_eq!(
818 res,
819 [Chunk::Crc32 { checksum: 1234 }, Chunk::Fill { start: 0, size: 4096, value: 1 }]
820 );
821 }
822 }
823
824 #[test]
829 fn test_roundtrip() {
830 let tmpdir = TempDir::new().unwrap();
831
832 let (mut file, _temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
834 let mut rng = SmallRng::from_entropy();
835 let mut buf = Vec::<u8>::new();
836 buf.resize(1 * 4096, 0);
837 rng.fill_bytes(&mut buf);
838 file.write_all(&buf).unwrap();
839 file.flush().unwrap();
840 file.seek(SeekFrom::Start(0)).unwrap();
841 let content_size = buf.len();
842
843 let mut sparse_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
845 SparseImageBuilder::new()
846 .add_chunk(DataSource::Buffer(Box::new([0xffu8; 8192])))
847 .add_chunk(DataSource::Reader(Box::new(file)))
848 .add_chunk(DataSource::Fill(0xaaaa_aaaau32, 1024))
849 .add_chunk(DataSource::Skip(16384))
850 .build(&mut sparse_file)
851 .expect("Build sparse image failed");
852 sparse_file.seek(SeekFrom::Start(0)).unwrap();
853
854 let mut orig_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
855 unsparse(&mut sparse_file, &mut orig_file).expect("unsparse failed");
856 orig_file.seek(SeekFrom::Start(0)).unwrap();
857
858 let mut unsparsed_bytes = vec![];
859 orig_file.read_to_end(&mut unsparsed_bytes).expect("Failed to read unsparsed image");
860 assert_eq!(unsparsed_bytes.len(), 8192 + 20480 + content_size);
861 assert_eq!(&unsparsed_bytes[..8192], &[0xffu8; 8192]);
862 assert_eq!(&unsparsed_bytes[8192..8192 + content_size], &buf[..]);
863 assert_eq!(&unsparsed_bytes[8192 + content_size..12288 + content_size], &[0xaau8; 4096]);
864 assert_eq!(&unsparsed_bytes[12288 + content_size..], &[0u8; 16384]);
865 }
866}