Skip to main content

sparse/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#[cfg(target_endian = "big")]
6assert!(false, "This library assumes little-endian!");
7
8pub mod builder;
9mod format;
10pub mod reader;
11
12use crate::format::{CHUNK_HEADER_SIZE, ChunkHeader, SparseHeader};
13use crate::reader::SparseReader;
14
15use core::fmt;
16use serde::de::DeserializeOwned;
17use thiserror::Error;
18
19use std::fs::File;
20use std::io::{Cursor, Read, Seek, SeekFrom, Write};
21use std::path::Path;
22use tempfile::{NamedTempFile, TempPath};
23#[cfg(target_os = "fuchsia")]
24use zx;
25
26// Size of blocks to write.  Note that the format supports varied block sizes; this is the preferred
27// size by this library.
28const BLK_SIZE: u32 = 0x1000;
29
30#[derive(Debug, Clone, Copy)]
31pub enum SparseDataType {
32    Header,
33    ChunkHeader,
34    FillValue,
35    Checksum,
36}
37
38impl std::fmt::Display for SparseDataType {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            Self::Header => write!(f, "header"),
42            Self::ChunkHeader => write!(f, "chunk header"),
43            Self::FillValue => write!(f, "fill value"),
44            Self::Checksum => write!(f, "checksum"),
45        }
46    }
47}
48
49#[derive(Debug, Error)]
50pub enum DeserializeError {
51    #[error("IO error: {0}")]
52    Io(#[from] std::io::Error),
53
54    #[error("Bincode error: {0}")]
55    Bincode(#[from] Box<bincode::ErrorKind>),
56}
57
58#[derive(Debug, Clone, Copy, thiserror::Error)]
59pub enum UnalignedSource {
60    #[error("buffer length {0}")]
61    Buffer(usize),
62
63    #[error("Reader length {0}")]
64    Reader(u64),
65
66    #[error("Skip length {0}")]
67    Skip(u64),
68
69    #[error("Fill length {0}")]
70    Fill(u64),
71
72    #[error("Vmo size {0}")]
73    Vmo(u64),
74}
75
76#[derive(Debug, Error)]
77pub enum SparseError {
78    #[error("IO error: {0}")]
79    Io(#[from] std::io::Error),
80
81    #[error("Failed to deserialize {ty}: {source}")]
82    Deserialize {
83        ty: SparseDataType,
84        #[source]
85        source: DeserializeError,
86    },
87
88    #[error("Failed to serialize {ty}: {source}")]
89    Serialize {
90        ty: SparseDataType,
91        #[source]
92        source: Box<bincode::ErrorKind>,
93    },
94
95    #[error("Invalid sparse image header")]
96    InvalidHeader,
97
98    #[error("Invalid chunk header")]
99    InvalidChunkHeader,
100
101    #[error("Invalid chunk type {0}")]
102    InvalidChunkType(u16),
103
104    #[error("Given maximum download size ({0}) is less than the block size ({1})")]
105    MaxDownloadSizeTooSmall(u64, u32),
106
107    #[error("No source for Raw chunk")]
108    NoSourceForRawChunk,
109
110    #[error("Chunk is not block aligned")]
111    UnalignedChunk,
112
113    #[error("Failed to copy contents: {0}")]
114    CopyContents(#[source] std::io::Error),
115
116    #[error("Failed to fill contents: {0}")]
117    FillContents(#[source] std::io::Error),
118
119    #[error("Failed to skip contents: {0}")]
120    SkipContents(#[source] std::io::Error),
121
122    #[cfg(target_os = "fuchsia")]
123    #[error("Zircon error: {0}")]
124    Zircon(#[from] zx::Status),
125
126    #[error("Invalid {0}")]
127    UnalignedDataSource(UnalignedSource),
128
129    #[error("Sparse image would contain too many blocks")]
130    TooManyBlocks,
131}
132
133fn deserialize_from<'a, T: DeserializeOwned, R: Read + ?Sized>(
134    source: &mut R,
135) -> Result<T, DeserializeError> {
136    let mut buf = vec![0u8; std::mem::size_of::<T>()];
137    source.read_exact(&mut buf[..])?;
138    bincode::deserialize(&buf[..]).map_err(Into::into)
139}
140
141/// A union trait for `Write` and `Seek` that also allows truncation.
142pub trait Writer: Write + Seek {
143    /// Sets the length of the output stream.
144    fn set_len(&mut self, size: u64) -> Result<(), SparseError>;
145}
146
147impl Writer for File {
148    fn set_len(&mut self, size: u64) -> Result<(), SparseError> {
149        File::set_len(self, size).map_err(SparseError::from)
150    }
151}
152
153impl Writer for Cursor<Vec<u8>> {
154    fn set_len(&mut self, size: u64) -> Result<(), SparseError> {
155        Vec::resize(self.get_mut(), size as usize, 0u8);
156        Ok(())
157    }
158}
159
160// A wrapper around a Reader, which makes it seem like the underlying stream is only self.1 bytes
161// long.  The underlying reader is still advanced upon reading.
162// This is distinct from `std::io::Take` in that it does not modify the seek offset of the
163// underlying reader.  In other words, `LimitedReader` can be used to read a window within the
164// reader (by setting seek offset to the start, and the size limit to the end).
165struct LimitedReader<'a, R>(pub &'a mut R, pub usize);
166
167impl<'a, R: Read + Seek> Read for LimitedReader<'a, R> {
168    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
169        let offset = self.0.stream_position()?;
170        let avail = self.1.saturating_sub(offset as usize);
171        let to_read = std::cmp::min(avail, buf.len());
172        self.0.read(&mut buf[..to_read])
173    }
174}
175
176/// Returns whether the image in `reader` appears to be in the sparse format.
177pub fn is_sparse_image<R: Read + Seek>(reader: &mut R) -> bool {
178    || -> Option<bool> {
179        let header: SparseHeader = deserialize_from(reader).ok()?;
180        let is_sparse = header.magic == format::SPARSE_HEADER_MAGIC;
181        reader.seek(SeekFrom::Start(0)).ok()?;
182        Some(is_sparse)
183    }()
184    .unwrap_or(false)
185}
186
187#[derive(Clone, PartialEq, Debug)]
188pub enum Chunk {
189    /// `Raw` represents a set of blocks to be written to disk as-is.
190    /// `start` is the offset in the expanded image at which the Raw section starts.
191    /// `start` and `size` are in bytes, but must be block-aligned.
192    Raw { start: u64, size: u64 },
193    /// `Fill` represents a Chunk that has the `value` repeated enough to fill `size` bytes.
194    /// `start` is the offset in the expanded image at which the Fill section starts.
195    /// `start` and `size` are in bytes, but must be block-aligned.
196    Fill { start: u64, size: u64, value: u32 },
197    /// `DontCare` represents a set of blocks that need to be "offset" by the
198    /// image recipient.  If an image needs to be broken up into two sparse images, and we flash n
199    /// bytes for Sparse Image 1, Sparse Image 2 needs to start with a DontCareChunk with
200    /// (n/blocksize) blocks as its "size" property.
201    /// `start` is the offset in the expanded image at which the DontCare section starts.
202    /// `start` and `size` are in bytes, but must be block-aligned.
203    DontCare { start: u64, size: u64 },
204    /// `Crc32Chunk` is used as a checksum of a given set of Chunks for a SparseImage.  This is not
205    /// required and unused in most implementations of the Sparse Image format. The type is included
206    /// for completeness. It has 4 bytes of CRC32 checksum as describable in a u32.
207    #[allow(dead_code)]
208    Crc32 { checksum: u32 },
209}
210
211impl Chunk {
212    /// Attempts to read a `Chunk` from `reader`.  The reader will be positioned at the first byte
213    /// following the chunk header and any extra data; for a Raw chunk this means it will point at
214    /// the data payload, and for other chunks it will point at the next chunk header (or EOF).
215    /// `offset` is the current offset in the logical volume.
216    pub fn read_metadata<R: Read>(
217        reader: &mut R,
218        offset: u64,
219        block_size: u32,
220    ) -> Result<Self, SparseError> {
221        let header: ChunkHeader = deserialize_from(reader)
222            .map_err(|e| SparseError::Deserialize { ty: SparseDataType::ChunkHeader, source: e })?;
223        if !header.valid() {
224            return Err(SparseError::InvalidChunkHeader);
225        }
226
227        let size = header.chunk_sz as u64 * block_size as u64;
228        match header.chunk_type {
229            format::CHUNK_TYPE_RAW => Ok(Self::Raw { start: offset, size }),
230            format::CHUNK_TYPE_FILL => {
231                let value: u32 = deserialize_from(reader).map_err(|e| {
232                    SparseError::Deserialize { ty: SparseDataType::FillValue, source: e }
233                })?;
234                Ok(Self::Fill { start: offset, size, value })
235            }
236            format::CHUNK_TYPE_DONT_CARE => Ok(Self::DontCare { start: offset, size }),
237            format::CHUNK_TYPE_CRC32 => {
238                let checksum: u32 = deserialize_from(reader).map_err(|e| {
239                    SparseError::Deserialize { ty: SparseDataType::Checksum, source: e }
240                })?;
241                Ok(Self::Crc32 { checksum })
242            }
243            // We already validated the chunk_type in `ChunkHeader::is_valid`.
244            _ => unreachable!(),
245        }
246    }
247
248    fn valid(&self, block_size: u32) -> bool {
249        self.output_size() % (block_size as u64) == 0
250    }
251
252    /// Returns the offset into the logical image the chunk refers to, or None if the chunk has no
253    /// output data.
254    fn output_offset(&self) -> Option<u64> {
255        match self {
256            Self::Raw { start, .. } => Some(*start),
257            Self::Fill { start, .. } => Some(*start),
258            Self::DontCare { start, .. } => Some(*start),
259            Self::Crc32 { .. } => None,
260        }
261    }
262
263    /// Return number of bytes the chunk expands to when written to the partition.
264    fn output_size(&self) -> u64 {
265        match self {
266            Self::Raw { size, .. } => *size,
267            Self::Fill { size, .. } => *size,
268            Self::DontCare { size, .. } => *size,
269            Self::Crc32 { .. } => 0,
270        }
271    }
272
273    /// Return number of blocks the chunk expands to when written to the partition.
274    fn output_blocks(&self, block_size: u32) -> u32 {
275        self.output_size().div_ceil(block_size as u64) as u32
276    }
277
278    /// `chunk_type` returns the integer flag to represent the type of chunk
279    /// to use in the ChunkHeader
280    fn chunk_type(&self) -> u16 {
281        match self {
282            Self::Raw { .. } => format::CHUNK_TYPE_RAW,
283            Self::Fill { .. } => format::CHUNK_TYPE_FILL,
284            Self::DontCare { .. } => format::CHUNK_TYPE_DONT_CARE,
285            Self::Crc32 { .. } => format::CHUNK_TYPE_CRC32,
286        }
287    }
288
289    /// `chunk_data_len` returns the length of the chunk's header plus the
290    /// length of the data when serialized.
291    ///
292    /// This gets included in the sparse header and is encoded as a u32.
293    /// But while we are tracking the offsets and total sizes we need it to be
294    /// a u64 to help keep track of files that are greater than 4 GiB
295    fn chunk_data_len(&self) -> u32 {
296        let header_size = format::CHUNK_HEADER_SIZE;
297        let data_size = match self {
298            Self::Raw { size, .. } => *size as u32,
299            Self::Fill { .. } => std::mem::size_of::<u32>() as u32,
300            Self::DontCare { .. } => 0,
301            Self::Crc32 { .. } => std::mem::size_of::<u32>() as u32,
302        };
303        header_size.checked_add(data_size).unwrap()
304    }
305
306    /// Writes the chunk to the given Writer.  `source` is a Reader containing the data payload for
307    /// a Raw type chunk, with the seek offset pointing to the first byte of the data payload, and
308    /// with exactly enough bytes available for the rest of the data payload.
309    fn write<W: Write, R: Read>(
310        &self,
311        source: Option<&mut R>,
312        dest: &mut W,
313        block_size: u32,
314    ) -> Result<(), SparseError> {
315        if !self.valid(block_size) {
316            return Err(SparseError::UnalignedChunk);
317        }
318        let header = ChunkHeader::new(
319            self.chunk_type(),
320            0x0,
321            self.output_blocks(block_size),
322            self.chunk_data_len(),
323        );
324
325        bincode::serialize_into(&mut *dest, &header)
326            .map_err(|e| SparseError::Serialize { ty: SparseDataType::ChunkHeader, source: e })?;
327
328        match self {
329            Self::Raw { size, .. } => {
330                if source.is_none() {
331                    return Err(SparseError::NoSourceForRawChunk);
332                }
333                let n = std::io::copy(source.unwrap(), dest)?;
334                let size = *size as u64;
335                if n < size {
336                    let zeroes = vec![0u8; (size - n) as usize];
337                    dest.write_all(&zeroes)?;
338                }
339            }
340            Self::Fill { value, .. } => {
341                // Serialize the value,
342                bincode::serialize_into(dest, value).map_err(|e| SparseError::Serialize {
343                    ty: SparseDataType::FillValue,
344                    source: e,
345                })?;
346            }
347            Self::DontCare { .. } => {
348                // DontCare has no data to write
349            }
350            Self::Crc32 { checksum } => {
351                bincode::serialize_into(dest, checksum).map_err(|e| SparseError::Serialize {
352                    ty: SparseDataType::Checksum,
353                    source: e,
354                })?;
355            }
356        }
357        Ok(())
358    }
359}
360
361impl fmt::Display for Chunk {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        let message = match self {
364            Self::Raw { start, size } => {
365                format!("RawChunk: start: {}, total bytes: {}", start, size)
366            }
367            Self::Fill { start, size, value } => {
368                format!("FillChunk: start: {}, value: {}, n_blocks: {}", start, value, size)
369            }
370            Self::DontCare { start, size } => {
371                format!("DontCareChunk: start: {}, bytes: {}", start, size)
372            }
373            Self::Crc32 { checksum } => format!("Crc32Chunk: checksum: {:?}", checksum),
374        };
375        write!(f, "{}", message)
376    }
377}
378
379/// Chunk::write takes an Option of something that implements Read. The compiler still requires a
380/// concrete type for the generic argument even when the Option is None. This constant can be used
381/// in place of None to avoid having to specify a type for the source.
382pub const NO_SOURCE: Option<&mut Cursor<&[u8]>> = None;
383
384/// An in-memory description of an Android sparse image file.
385///
386/// Holds a sequence of [`Chunk`] definitions that describe how unsparsed image data
387/// should be formatted or partitioned. Can be serialized to a destination writer or
388/// lazily read via a [`SparseSliceReader`].
389#[derive(Clone, Debug, PartialEq)]
390pub struct SparseFileWriter {
391    /// The sequence of chunks that make up the sparse image.
392    pub chunks: Vec<Chunk>,
393}
394
395impl SparseFileWriter {
396    /// Creates a new `SparseFileWriter` from a sequence of [`Chunk`]s.
397    pub fn new(chunks: Vec<Chunk>) -> SparseFileWriter {
398        SparseFileWriter { chunks }
399    }
400
401    /// Returns the total number of blocks represented by all chunks in this sparse image.
402    pub fn total_blocks(&self) -> u32 {
403        self.chunks.iter().map(|c| c.output_blocks(BLK_SIZE)).sum()
404    }
405
406    /// Returns the total unsparsed size in bytes represented by this sparse image.
407    pub fn total_bytes(&self) -> u64 {
408        self.chunks.iter().map(|c| c.output_size() as u64).sum()
409    }
410
411    /// Returns the total serialized size (in bytes) of the sparse image file,
412    /// including the file header, all chunk headers, and chunk payloads.
413    pub fn file_size(&self) -> u64 {
414        let mut size = format::SPARSE_HEADER_SIZE as u64;
415        for chunk in &self.chunks {
416            size += chunk.chunk_data_len() as u64;
417        }
418        size
419    }
420
421    /// Creates an `io::Read` stream that lazily reads the serialized sparse image bytes
422    /// directly from `source` without creating an intermediate file on disk.
423    ///
424    /// # Errors
425    ///
426    /// Returns [`SparseError::UnalignedChunk`] if any chunk is not aligned to the block size,
427    /// or [`SparseError::Serialize`] if header serialization fails.
428    pub fn slice_reader<'a, R: Read + Seek>(
429        &'a self,
430        source: &'a mut R,
431    ) -> Result<SparseSliceReader<'a, R>, SparseError> {
432        SparseSliceReader::new(self, source)
433    }
434
435    /// Writes the serialized sparse image to `writer`, reading raw payload data from `reader`.
436    ///
437    /// # Errors
438    ///
439    /// Returns an error if writing to `writer` or seeking/reading from `reader` fails,
440    /// or if any chunk is invalid or cannot be serialized.
441    pub fn write<W: Write + Seek, R: Read + Seek>(
442        &self,
443        reader: &mut R,
444        writer: &mut W,
445    ) -> Result<(), SparseError> {
446        let header = SparseHeader::new(
447            BLK_SIZE.try_into().unwrap(),          // Size of the blocks
448            self.total_blocks(),                   // Total blocks in this image
449            self.chunks.len().try_into().unwrap(), // Total chunks in this image
450        );
451
452        bincode::serialize_into(&mut *writer, &header)
453            .map_err(|e| SparseError::Serialize { ty: SparseDataType::Header, source: e })?;
454
455        for chunk in &self.chunks {
456            let mut reader = if let &Chunk::Raw { start, size } = chunk {
457                if reader.stream_position()? != start {
458                    reader.seek(SeekFrom::Start(start))?;
459                }
460                Some(LimitedReader(reader, start as usize + size as usize))
461            } else {
462                None
463            };
464            chunk.write(reader.as_mut(), writer, BLK_SIZE)?;
465        }
466
467        Ok(())
468    }
469}
470
471/// `SparseSliceReader` is an `io::Read` stream that lazily emits the binary
472/// Android Sparse Image serialization (headers and payload) for a single
473/// `SparseFileWriter` slice directly from the underlying source reader without
474/// intermediate disk files.
475pub struct SparseSliceReader<'a, R> {
476    source: &'a mut R,
477    header_bytes: Vec<u8>,
478    header_pos: usize,
479    chunks: &'a [Chunk],
480    chunk_idx: usize,
481    chunk_header_bytes: Vec<u8>,
482    chunk_header_pos: usize,
483    payload_pos: u64,
484}
485
486impl<'a, R: Read + Seek> SparseSliceReader<'a, R> {
487    /// Creates a new `SparseSliceReader` that lazily streams the serialized representation
488    /// of `writer` using payload bytes from `source`.
489    ///
490    /// # Errors
491    ///
492    /// Returns [`SparseError::UnalignedChunk`] if any chunk is not block-aligned,
493    /// or [`SparseError::Serialize`] if header serialization fails.
494    pub fn new(writer: &'a SparseFileWriter, source: &'a mut R) -> Result<Self, SparseError> {
495        let header = SparseHeader::new(
496            BLK_SIZE.try_into().unwrap(),
497            writer.total_blocks(),
498            writer.chunks.len().try_into().unwrap(),
499        );
500        let header_bytes = bincode::serialize(&header)
501            .map_err(|e| SparseError::Serialize { ty: SparseDataType::Header, source: e })?;
502        let mut reader = Self {
503            source,
504            header_bytes,
505            header_pos: 0,
506            chunks: &writer.chunks,
507            chunk_idx: 0,
508            chunk_header_bytes: Vec::new(),
509            chunk_header_pos: 0,
510            payload_pos: 0,
511        };
512        reader.prepare_next_chunk_header()?;
513        Ok(reader)
514    }
515
516    fn prepare_next_chunk_header(&mut self) -> Result<(), SparseError> {
517        if self.chunk_idx < self.chunks.len() {
518            let chunk = &self.chunks[self.chunk_idx];
519            if !chunk.valid(BLK_SIZE) {
520                return Err(SparseError::UnalignedChunk);
521            }
522            let header = ChunkHeader::new(
523                chunk.chunk_type(),
524                0x0,
525                chunk.output_blocks(BLK_SIZE),
526                chunk.chunk_data_len(),
527            );
528            self.chunk_header_bytes = bincode::serialize(&header).map_err(|e| {
529                SparseError::Serialize { ty: SparseDataType::ChunkHeader, source: e }
530            })?;
531            self.chunk_header_pos = 0;
532            self.payload_pos = 0;
533        }
534        Ok(())
535    }
536}
537
538impl<'a, R: Read + Seek> Read for SparseSliceReader<'a, R> {
539    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
540        if buf.is_empty() {
541            return Ok(0);
542        }
543
544        let mut total_written = 0;
545
546        while total_written < buf.len() {
547            // 1. Emit SparseHeader bytes if remaining
548            if self.header_pos < self.header_bytes.len() {
549                let to_copy = std::cmp::min(
550                    buf.len() - total_written,
551                    self.header_bytes.len() - self.header_pos,
552                );
553                buf[total_written..total_written + to_copy].copy_from_slice(
554                    &self.header_bytes[self.header_pos..self.header_pos + to_copy],
555                );
556                self.header_pos += to_copy;
557                total_written += to_copy;
558                continue;
559            }
560
561            if self.chunk_idx >= self.chunks.len() {
562                break;
563            }
564
565            // 2a. Emit ChunkHeader bytes if remaining
566            if self.chunk_header_pos < self.chunk_header_bytes.len() {
567                let to_copy = std::cmp::min(
568                    buf.len() - total_written,
569                    self.chunk_header_bytes.len() - self.chunk_header_pos,
570                );
571                buf[total_written..total_written + to_copy].copy_from_slice(
572                    &self.chunk_header_bytes
573                        [self.chunk_header_pos..self.chunk_header_pos + to_copy],
574                );
575                self.chunk_header_pos += to_copy;
576                total_written += to_copy;
577                continue;
578            }
579
580            // 2b. Emit Chunk payload
581            let chunk = &self.chunks[self.chunk_idx];
582            match chunk {
583                Chunk::Raw { start, size } => {
584                    let remaining_payload = *size - self.payload_pos;
585                    if remaining_payload > 0 {
586                        if self.payload_pos == 0 {
587                            if self.source.stream_position()? != *start {
588                                self.source.seek(SeekFrom::Start(*start))?;
589                            }
590                        }
591                        let to_read =
592                            std::cmp::min(buf.len() - total_written, remaining_payload as usize);
593                        let n =
594                            self.source.read(&mut buf[total_written..total_written + to_read])?;
595                        if n == 0 {
596                            // If EOF reached on source earlier than expected, fill remainder with zeroes
597                            let zeroes = std::cmp::min(
598                                buf.len() - total_written,
599                                remaining_payload as usize,
600                            );
601                            buf[total_written..total_written + zeroes].fill(0);
602                            self.payload_pos += zeroes as u64;
603                            total_written += zeroes;
604                            if self.payload_pos >= *size {
605                                self.chunk_idx += 1;
606                                self.prepare_next_chunk_header().map_err(|e| {
607                                    std::io::Error::new(std::io::ErrorKind::Other, e)
608                                })?;
609                            }
610                            continue;
611                        }
612                        self.payload_pos += n as u64;
613                        total_written += n;
614                        if self.payload_pos >= *size {
615                            self.chunk_idx += 1;
616                            self.prepare_next_chunk_header()
617                                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
618                        }
619                        return Ok(total_written);
620                    }
621                }
622                Chunk::Fill { value, .. } => {
623                    if self.payload_pos < 4 {
624                        let val_bytes = value.to_le_bytes();
625                        let remaining = 4 - self.payload_pos as usize;
626                        let to_copy = std::cmp::min(buf.len() - total_written, remaining);
627                        let start = self.payload_pos as usize;
628                        buf[total_written..total_written + to_copy]
629                            .copy_from_slice(&val_bytes[start..start + to_copy]);
630                        self.payload_pos += to_copy as u64;
631                        total_written += to_copy;
632                        if self.payload_pos >= 4 {
633                            self.chunk_idx += 1;
634                            self.prepare_next_chunk_header()
635                                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
636                        }
637                        continue;
638                    }
639                }
640                Chunk::Crc32 { checksum } => {
641                    if self.payload_pos < 4 {
642                        let val_bytes = checksum.to_le_bytes();
643                        let remaining = 4 - self.payload_pos as usize;
644                        let to_copy = std::cmp::min(buf.len() - total_written, remaining);
645                        let start = self.payload_pos as usize;
646                        buf[total_written..total_written + to_copy]
647                            .copy_from_slice(&val_bytes[start..start + to_copy]);
648                        self.payload_pos += to_copy as u64;
649                        total_written += to_copy;
650                        if self.payload_pos >= 4 {
651                            self.chunk_idx += 1;
652                            self.prepare_next_chunk_header()
653                                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
654                        }
655                        continue;
656                    }
657                }
658                Chunk::DontCare { .. } => {
659                    self.chunk_idx += 1;
660                    self.prepare_next_chunk_header()
661                        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
662                    continue;
663                }
664            }
665
666            self.chunk_idx += 1;
667            self.prepare_next_chunk_header()
668                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
669        }
670
671        Ok(total_written)
672    }
673}
674
675impl fmt::Display for SparseFileWriter {
676    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677        write!(f, r"SparseFileWriter: {} Chunks:", self.chunks.len())
678    }
679}
680
681/// `add_sparse_chunk` takes the input vec, v and given `Chunk`, chunk, and
682/// attempts to add the chunk to the end of the vec. If the current last chunk
683/// is the same kind of Chunk as the `chunk`, then it will merge the two chunks
684/// into one chunk.
685///
686/// Example: A `FillChunk` with value 0 and size 1 is the last chunk
687/// in `v`, and `chunk` is a FillChunk with value 0 and size 1, after this,
688/// `v`'s last element will be a FillChunk with value 0 and size 2.
689fn add_sparse_chunk(r: &mut Vec<Chunk>, chunk: Chunk) {
690    match r.last_mut() {
691        // We've got something in the Vec... if they are both the same type,
692        // merge them, otherwise, just push the new one
693        Some(last) => match (&last, &chunk) {
694            (Chunk::Raw { start, size }, Chunk::Raw { size: new_length, .. })
695                if size.checked_add(*new_length).is_some() =>
696            {
697                *last = Chunk::Raw { start: *start, size: size + new_length };
698                return;
699            }
700            (
701                Chunk::Fill { start, size, value },
702                Chunk::Fill { size: new_size, value: new_value, .. },
703            ) if value == new_value && size.checked_add(*new_size).is_some() => {
704                *last = Chunk::Fill { start: *start, size: size + new_size, value: *value };
705                return;
706            }
707            (Chunk::DontCare { start, size }, Chunk::DontCare { size: new_size, .. })
708                if size.checked_add(*new_size).is_some() =>
709            {
710                *last = Chunk::DontCare { start: *start, size: size + new_size };
711                return;
712            }
713            _ => {}
714        },
715        None => {}
716    }
717
718    // If the chunk types differ they cannot be merged.
719    // If they are both Fill but have different values, they cannot be merged.
720    // Crc32 cannot be merged.
721    // If we don't have any chunks then we add it
722    r.push(chunk);
723}
724
725/// Reads a sparse image from `source` and expands it to its unsparsed representation in `dest`.
726pub fn unsparse<W: Writer, R: Read + Seek>(
727    source: &mut R,
728    dest: &mut W,
729) -> Result<(), SparseError> {
730    let header: SparseHeader = deserialize_from(source)
731        .map_err(|e| SparseError::Deserialize { ty: SparseDataType::Header, source: e })?;
732    if !header.valid() {
733        return Err(SparseError::InvalidHeader);
734    }
735
736    for _ in 0..header.total_chunks {
737        expand_chunk(source, dest, header.blk_sz)?;
738    }
739    // Truncate output to its current seek offset, in case the last chunk we wrote was DontNeed.
740    let offset = dest.stream_position()?;
741    dest.set_len(offset)?;
742    dest.flush()?;
743    Ok(())
744}
745
746/// Reads a chunk from `source`, and expands it, writing the result to `dest`.
747fn expand_chunk<R: Read + Seek, W: Write + Seek>(
748    source: &mut R,
749    dest: &mut W,
750    block_size: u32,
751) -> Result<(), SparseError> {
752    let header: ChunkHeader = deserialize_from(source)
753        .map_err(|e| SparseError::Deserialize { ty: SparseDataType::ChunkHeader, source: e })?;
754    if !header.valid() {
755        return Err(SparseError::InvalidChunkHeader);
756    }
757    let size = (header.chunk_sz * block_size) as usize;
758    match header.chunk_type {
759        format::CHUNK_TYPE_RAW => {
760            let limit = source.stream_position()? as usize + size;
761            std::io::copy(&mut LimitedReader(source, limit), dest)
762                .map_err(SparseError::CopyContents)?;
763        }
764        format::CHUNK_TYPE_FILL => {
765            let value: [u8; 4] = deserialize_from(source).map_err(|e| {
766                SparseError::Deserialize { ty: SparseDataType::FillValue, source: e }
767            })?;
768            assert!(size % 4 == 0);
769            let repeated = value.repeat(size / 4);
770            dest.write_all(&repeated).map_err(SparseError::FillContents)?;
771        }
772        format::CHUNK_TYPE_DONT_CARE => {
773            dest.seek(SeekFrom::Current(size as i64)).map_err(SparseError::SkipContents)?;
774        }
775        format::CHUNK_TYPE_CRC32 => {
776            let _: u32 = deserialize_from(source).map_err(|e| SparseError::Deserialize {
777                ty: SparseDataType::Checksum,
778                source: e,
779            })?;
780        }
781        _ => return Err(SparseError::InvalidChunkType(header.chunk_type)),
782    };
783    Ok(())
784}
785
786/// Takes a `sparse_file` and breaks it into multiple `SparseFileWriter` slices whose
787/// serialized file size will not exceed `max_download_size`.
788///
789/// # Arguments
790///
791/// * `sparse_file` - The sparse file writer containing chunk definitions to resparse.
792/// * `max_download_size` - Maximum size in bytes for each resparsed slice.
793///
794/// # Errors
795///
796/// Returns [`SparseError::MaxDownloadSizeTooSmall`] if `max_download_size` is less than or
797/// equal to the block size ([`BLK_SIZE`]).
798pub fn resparse(
799    sparse_file: SparseFileWriter,
800    max_download_size: u64,
801) -> Result<Vec<SparseFileWriter>, SparseError> {
802    if max_download_size <= BLK_SIZE as u64 {
803        return Err(SparseError::MaxDownloadSizeTooSmall(max_download_size, BLK_SIZE));
804    }
805    let mut ret = Vec::<SparseFileWriter>::new();
806
807    // File length already starts with a header for the SparseFile as
808    // well as the size of a potential DontCare and Crc32 Chunk
809    let sunk_file_length = format::SPARSE_HEADER_SIZE as u64
810        + Chunk::DontCare { start: 0, size: BLK_SIZE.into() }.chunk_data_len() as u64
811        + Chunk::Crc32 { checksum: 2345 }.chunk_data_len() as u64;
812
813    let total_image_bytes = sparse_file.total_bytes();
814    let mut chunk_pos = 0;
815    let mut offset_in_raw = 0u64;
816    let mut output_offset = 0u64;
817
818    while chunk_pos < sparse_file.chunks.len() {
819        log::trace!(
820            "Starting a new file at chunk position: {}, offset_in_raw: {}",
821            chunk_pos,
822            offset_in_raw
823        );
824
825        let mut file_len = sunk_file_length;
826        let mut chunks = Vec::<Chunk>::new();
827
828        if output_offset > 0 {
829            // If we already have written bytes... add a DontCare block to
830            // move the pointer
831            log::trace!("Adding a DontCare chunk offset: {}", output_offset);
832            let dont_care = Chunk::DontCare { start: 0, size: output_offset };
833            chunks.push(dont_care);
834        }
835
836        loop {
837            if chunk_pos >= sparse_file.chunks.len() {
838                log::trace!("Finished iterating chunks");
839                break;
840            }
841
842            let chunk = &sparse_file.chunks[chunk_pos];
843            match chunk {
844                Chunk::Raw { start, size } => {
845                    let remaining_raw = *size - offset_in_raw;
846                    let chunk_header_len = format::CHUNK_HEADER_SIZE as u64;
847                    let available_in_file = max_download_size.saturating_sub(file_len);
848
849                    if available_in_file < chunk_header_len + BLK_SIZE as u64 {
850                        // Cannot fit even one block in current file.
851                        let remainder_size = total_image_bytes.saturating_sub(output_offset);
852                        if remainder_size > 0 {
853                            let dont_care =
854                                Chunk::DontCare { start: output_offset, size: remainder_size };
855                            chunks.push(dont_care);
856                        }
857                        break;
858                    }
859
860                    let max_raw_payload = ((available_in_file - chunk_header_len)
861                        / BLK_SIZE as u64)
862                        * BLK_SIZE as u64;
863                    let to_take = std::cmp::min(remaining_raw, max_raw_payload);
864
865                    if to_take == 0 {
866                        let remainder_size = total_image_bytes.saturating_sub(output_offset);
867                        if remainder_size > 0 {
868                            let dont_care =
869                                Chunk::DontCare { start: output_offset, size: remainder_size };
870                            chunks.push(dont_care);
871                        }
872                        break;
873                    }
874
875                    let sub_chunk = Chunk::Raw { start: *start + offset_in_raw, size: to_take };
876                    add_sparse_chunk(&mut chunks, sub_chunk);
877                    file_len += chunk_header_len + to_take;
878                    output_offset += to_take;
879                    offset_in_raw += to_take;
880
881                    if offset_in_raw == *size {
882                        chunk_pos += 1;
883                        offset_in_raw = 0;
884                    } else {
885                        // Current file is full.
886                        let remainder_size = total_image_bytes.saturating_sub(output_offset);
887                        if remainder_size > 0 {
888                            let dont_care =
889                                Chunk::DontCare { start: output_offset, size: remainder_size };
890                            chunks.push(dont_care);
891                        }
892                        break;
893                    }
894                }
895                other => {
896                    let curr_chunk_data_len = other.chunk_data_len() as u64;
897                    if (file_len + curr_chunk_data_len) > max_download_size {
898                        let remainder_size = total_image_bytes.saturating_sub(output_offset);
899                        if remainder_size > 0 {
900                            let dont_care =
901                                Chunk::DontCare { start: output_offset, size: remainder_size };
902                            chunks.push(dont_care);
903                        }
904                        break;
905                    }
906
907                    add_sparse_chunk(&mut chunks, other.clone());
908                    file_len += curr_chunk_data_len;
909                    output_offset += other.output_size() as u64;
910                    chunk_pos += 1;
911                }
912            }
913        }
914
915        let resparsed = SparseFileWriter::new(chunks);
916        log::trace!("resparse: Adding new SparseFile: {}", resparsed);
917        ret.push(resparsed);
918    }
919
920    Ok(ret)
921}
922
923/// Takes a provided `reader` and generates a set of `SparseFileWriter`s representing
924/// the resparsed slices with the provided `max_download_size` constraining slice size.
925///
926/// # Arguments
927///
928/// * `reader` - The sparse reader of an existing sparse file.
929/// * `max_download_size` - Maximum size in bytes that can be downloaded by the device for each slice.
930///
931/// # Errors
932///
933/// Returns [`SparseError::MaxDownloadSizeTooSmall`] if `max_download_size` is less than or
934/// equal to the block size ([`BLK_SIZE`]).
935pub fn resparse_sparse_img_writers<R: Read + std::io::Seek>(
936    reader: &mut SparseReader<R>,
937    max_download_size: u64,
938) -> Result<Vec<SparseFileWriter>, SparseError> {
939    log::debug!("Building writers from Reader");
940    let mut chunks = vec![];
941    for (chunk, _offset) in reader.chunks() {
942        chunks.push(chunk.clone());
943    }
944    let sparse_file = SparseFileWriter::new(chunks);
945    resparse(sparse_file, max_download_size)
946}
947
948/// Takes a provided `reader` and generates a set of temporary files in `dir`
949/// in the Sparse image format. With the provided `max_download_size`
950/// constraining file size.
951///
952/// # Arguments
953///
954/// * `reader` - The Sparse Reader of a Sparse File
955/// * `dir` - Path to the directory to write the Sparse file(s).
956/// * `max_download_size` - Maximum size that can be downloaded by the device.
957pub fn resparse_sparse_img<R: Read + std::io::Seek>(
958    reader: &mut SparseReader<R>,
959    dir: &Path,
960    max_download_size: u64,
961) -> Result<Vec<TempPath>, SparseError> {
962    let mut ret = Vec::<TempPath>::new();
963    log::debug!("Resparsing sparse file");
964    for re_sparsed_file in resparse_sparse_img_writers(reader, max_download_size)? {
965        let (file, temp_path) = NamedTempFile::new_in(dir)?.into_parts();
966        let mut file_create = File::from(file);
967
968        log::debug!("Writing resparsed {} to disk", re_sparsed_file);
969        re_sparsed_file.write(reader, &mut file_create)?;
970
971        ret.push(temp_path);
972    }
973
974    log::debug!("Finished building sparse files");
975
976    Ok(ret)
977}
978
979/// Returns the fill value if the entire slice consists of a single repeated 32-bit integer.
980/// Returns None if the slice is empty, not a multiple of 4 bytes, or contains varying values.
981#[inline]
982pub(crate) fn find_fill_value(buf: &[u8]) -> Option<u32> {
983    if buf.len() < 4 || buf.len() % 4 != 0 {
984        return None;
985    }
986    let first = u32::from_le_bytes(buf[0..4].try_into().unwrap());
987    for chunk in buf[4..].chunks_exact(4) {
988        if u32::from_le_bytes(chunk.try_into().unwrap()) != first {
989            return None;
990        }
991    }
992    Some(first)
993}
994
995/// Takes the given `file_to_upload` for the `named` partition and generates `SparseFileWriter`
996/// slices on the fly, emitting each slice as soon as it reaches `max_download_size`.
997///
998/// # Arguments
999///
1000/// * `name` - Name of the partition for the image. Used for logs only.
1001/// * `file_to_upload` - Path to the file to translate to sparse image format.
1002/// * `max_download_size` - Maximum size that can be downloaded by the device for each slice.
1003/// * `on_slice` - Callback invoked with each `SparseFileWriter` slice as it is completed.
1004///
1005/// # Errors
1006///
1007/// Returns [`SparseError::MaxDownloadSizeTooSmall`] if `max_download_size` is less than or
1008/// equal to the block size ([`BLK_SIZE`]), or an I/O error if `file_to_upload` fails to open
1009/// or read.
1010pub fn build_sparse_writers_streaming<F>(
1011    name: &str,
1012    file_to_upload: &str,
1013    max_download_size: u64,
1014    mut on_slice: F,
1015) -> Result<(), SparseError>
1016where
1017    F: FnMut(SparseFileWriter) -> Result<(), SparseError>,
1018{
1019    if max_download_size <= BLK_SIZE.into() {
1020        return Err(SparseError::MaxDownloadSizeTooSmall(max_download_size, BLK_SIZE));
1021    }
1022    if BLK_SIZE as usize % std::mem::size_of::<u32>() != 0 {
1023        return Err(SparseError::UnalignedDataSource(UnalignedSource::Buffer(BLK_SIZE as usize)));
1024    }
1025    log::debug!("Building sparse writers (streaming) for: {}. File: {}", name, file_to_upload);
1026    let mut in_file = File::open(file_to_upload)?;
1027    let total_image_bytes = in_file.metadata()?.len().next_multiple_of(BLK_SIZE.into());
1028
1029    let sunk_file_length = u64::from(
1030        format::SPARSE_HEADER_SIZE
1031            + CHUNK_HEADER_SIZE
1032            + Chunk::Crc32 { checksum: 2345 }.chunk_data_len(),
1033    );
1034
1035    let mut current_slice_chunks = Vec::<Chunk>::new();
1036    let mut current_slice_file_len = sunk_file_length;
1037    let mut output_offset = 0u64;
1038    let mut total_read = 0usize;
1039
1040    let mut buf = [0u8; BLK_SIZE as usize];
1041    loop {
1042        let read = in_file.read(&mut buf)?;
1043        if read == 0 {
1044            break;
1045        }
1046        // Zero-fill remainder
1047        buf[read..].fill(0);
1048
1049        let start = total_read as u64;
1050        let size = buf.len().try_into().unwrap();
1051        let candidate_chunk = if let Some(value) = find_fill_value(&buf) {
1052            Chunk::Fill { start, size, value }
1053        } else {
1054            Chunk::Raw { start, size }
1055        };
1056
1057        let candidate_data_len = u64::from(candidate_chunk.chunk_data_len());
1058
1059        if current_slice_file_len + candidate_data_len > max_download_size {
1060            let remainder_size = total_image_bytes.saturating_sub(output_offset);
1061            if remainder_size > 0 {
1062                let dont_care = Chunk::DontCare { start: output_offset, size: remainder_size };
1063                current_slice_chunks.push(dont_care);
1064            }
1065
1066            let slice_writer = SparseFileWriter::new(current_slice_chunks);
1067            log::trace!("Emitting completed sparse slice: {}", slice_writer);
1068            on_slice(slice_writer)?;
1069
1070            current_slice_chunks = if output_offset > 0 {
1071                vec![Chunk::DontCare { start: 0, size: output_offset }]
1072            } else {
1073                Vec::new()
1074            };
1075            current_slice_file_len = sunk_file_length + u64::from(CHUNK_HEADER_SIZE);
1076        }
1077
1078        add_sparse_chunk(&mut current_slice_chunks, candidate_chunk);
1079        current_slice_file_len += candidate_data_len;
1080        output_offset += buf.len() as u64;
1081        total_read += read;
1082    }
1083
1084    if !current_slice_chunks.is_empty() {
1085        let slice_writer = SparseFileWriter::new(current_slice_chunks);
1086        log::trace!("Emitting final sparse slice: {}", slice_writer);
1087        on_slice(slice_writer)?;
1088    }
1089
1090    Ok(())
1091}
1092
1093/// Takes the given `file_to_upload` for the `named` partition and creates a
1094/// set of `SparseFileWriter` slices in memory with the provided `max_download_size`
1095/// constraining slice size.
1096///
1097/// # Arguments
1098///
1099/// * `name` - Name of the partition for the image. Used for logs only.
1100/// * `file_to_upload` - Path to the file to translate to sparse image format.
1101/// * `max_download_size` - Maximum size that can be downloaded by the device for each slice.
1102///
1103/// # Errors
1104///
1105/// Returns [`SparseError::MaxDownloadSizeTooSmall`] if `max_download_size` is less than or
1106/// equal to the block size ([`BLK_SIZE`]), or an I/O error if `file_to_upload` fails to open
1107/// or read.
1108pub fn build_sparse_writers(
1109    name: &str,
1110    file_to_upload: &str,
1111    max_download_size: u64,
1112) -> Result<Vec<SparseFileWriter>, SparseError> {
1113    let mut writers = Vec::new();
1114    build_sparse_writers_streaming(name, file_to_upload, max_download_size, |writer| {
1115        writers.push(writer);
1116        Ok(())
1117    })?;
1118    Ok(writers)
1119}
1120
1121/// Takes the given `file_to_upload` for the `named` partition and creates a
1122/// set of temporary files in the given `dir` in Sparse Image Format. With the
1123/// provided `max_download_size` constraining file size.
1124///
1125/// # Arguments
1126///
1127/// * `name` - Name of the partition the image. Used for logs only.
1128/// * `file_to_upload` - Path to the file to translate to sparse image format.
1129/// * `dir` - Path to write the Sparse file(s).
1130/// * `max_download_size` - Maximum size that can be downloaded by the device.
1131pub fn build_sparse_files(
1132    name: &str,
1133    file_to_upload: &str,
1134    dir: &Path,
1135    max_download_size: u64,
1136) -> Result<Vec<TempPath>, SparseError> {
1137    let mut in_file = File::open(file_to_upload)?;
1138    let mut ret = Vec::<TempPath>::new();
1139    log::trace!("Resparsing sparse file");
1140    for re_sparsed_file in build_sparse_writers(name, file_to_upload, max_download_size)? {
1141        let (file, temp_path) = NamedTempFile::new_in(dir)?.into_parts();
1142        let mut file_create = File::from(file);
1143
1144        log::trace!("Writing resparsed {} to disk", re_sparsed_file);
1145        re_sparsed_file.write(&mut in_file, &mut file_create)?;
1146
1147        ret.push(temp_path);
1148    }
1149
1150    log::debug!("Finished building sparse files");
1151
1152    Ok(ret)
1153}
1154
1155////////////////////////////////////////////////////////////////////////////////
1156// tests
1157
1158#[cfg(test)]
1159mod test {
1160    #[cfg(target_os = "linux")]
1161    use crate::build_sparse_files;
1162
1163    use super::builder::{DataSource, SparseImageBuilder};
1164    use super::{
1165        BLK_SIZE, Chunk, NO_SOURCE, SparseFileWriter, add_sparse_chunk, resparse, unsparse,
1166    };
1167    use rand::Rng as _;
1168    use rand::rngs::SmallRng;
1169    use std::io::{Cursor, Read as _, Seek as _, SeekFrom, Write as _};
1170    #[cfg(target_os = "linux")]
1171    use std::path::Path;
1172    #[cfg(target_os = "linux")]
1173    use std::process::{Command, Stdio};
1174    use tempfile::{NamedTempFile, TempDir};
1175
1176    #[test]
1177    fn test_fill_into_bytes() {
1178        let mut dest = Cursor::new(Vec::<u8>::new());
1179
1180        let fill_chunk = Chunk::Fill { start: 0, size: (5 * BLK_SIZE).into(), value: 365 };
1181        fill_chunk.write(NO_SOURCE, &mut dest, BLK_SIZE).unwrap();
1182        assert_eq!(dest.into_inner(), [194, 202, 0, 0, 5, 0, 0, 0, 16, 0, 0, 0, 109, 1, 0, 0]);
1183    }
1184
1185    #[test]
1186    fn test_raw_into_bytes() {
1187        const EXPECTED_RAW_BYTES: [u8; 22] =
1188            [193, 202, 0, 0, 1, 0, 0, 0, 12, 16, 0, 0, 49, 50, 51, 52, 53, 0, 0, 0, 0, 0];
1189
1190        let mut source = Cursor::new(Vec::<u8>::from(&b"12345"[..]));
1191        let mut sparse = Cursor::new(Vec::<u8>::new());
1192        let chunk = Chunk::Raw { start: 0, size: BLK_SIZE.into() };
1193
1194        chunk.write(Some(&mut source), &mut sparse, BLK_SIZE).unwrap();
1195        let buf = sparse.into_inner();
1196        assert_eq!(buf.len(), 4108);
1197        assert_eq!(&buf[..EXPECTED_RAW_BYTES.len()], EXPECTED_RAW_BYTES);
1198        assert_eq!(&buf[EXPECTED_RAW_BYTES.len()..], &[0u8; 4108 - EXPECTED_RAW_BYTES.len()]);
1199    }
1200
1201    #[test]
1202    fn test_dont_care_into_bytes() {
1203        let mut dest = Cursor::new(Vec::<u8>::new());
1204        let chunk = Chunk::DontCare { start: 0, size: (5 * BLK_SIZE).into() };
1205
1206        chunk.write(NO_SOURCE, &mut dest, BLK_SIZE).unwrap();
1207        assert_eq!(dest.into_inner(), [195, 202, 0, 0, 5, 0, 0, 0, 12, 0, 0, 0]);
1208    }
1209
1210    #[test]
1211    fn test_sparse_file_into_bytes() {
1212        let mut source = Cursor::new(Vec::<u8>::from(&b"123"[..]));
1213        let mut sparse = Cursor::new(Vec::<u8>::new());
1214        let mut chunks = Vec::<Chunk>::new();
1215        // Add a fill chunk
1216        let fill = Chunk::Fill { start: 0, size: 4096, value: 5 };
1217        chunks.push(fill);
1218        // Add a raw chunk
1219        let raw = Chunk::Raw { start: 0, size: 12288 };
1220        chunks.push(raw);
1221        // Add a dontcare chunk
1222        let dontcare = Chunk::DontCare { start: 0, size: 4096 };
1223        chunks.push(dontcare);
1224
1225        let sparsefile = SparseFileWriter::new(chunks);
1226        sparsefile.write(&mut source, &mut sparse).unwrap();
1227
1228        sparse.seek(SeekFrom::Start(0)).unwrap();
1229        let mut unsparsed = Cursor::new(Vec::<u8>::new());
1230        unsparse(&mut sparse, &mut unsparsed).unwrap();
1231        let buf = unsparsed.into_inner();
1232        assert_eq!(buf.len(), 4096 + 12288 + 4096);
1233        {
1234            let chunks = buf[..4096].chunks(4);
1235            for chunk in chunks {
1236                assert_eq!(chunk, &[5u8, 0, 0, 0]);
1237            }
1238        }
1239        assert_eq!(&buf[4096..4099], b"123");
1240        assert_eq!(&buf[4099..16384], &[0u8; 12285]);
1241        assert_eq!(&buf[16384..], &[0u8; 4096]);
1242    }
1243
1244    ////////////////////////////////////////////////////////////////////////////
1245    // Tests for resparse
1246
1247    #[test]
1248    fn test_resparse_bails_on_too_small_size() {
1249        let sparse = SparseFileWriter::new(Vec::<Chunk>::new());
1250        assert!(resparse(sparse, 4095).is_err());
1251    }
1252
1253    #[test]
1254    fn test_resparse_splits() {
1255        let max_download_size = 4096 * 2;
1256
1257        let mut chunks = Vec::<Chunk>::new();
1258        chunks.push(Chunk::Raw { start: 0, size: 4096 });
1259        chunks.push(Chunk::Fill { start: 4096, size: 4096, value: 2 });
1260        // We want 2 sparse files with the second sparse file having a
1261        // DontCare chunk and then this chunk
1262        chunks.push(Chunk::Raw { start: 8192, size: 4096 });
1263
1264        let input_sparse_file = SparseFileWriter::new(chunks);
1265        let resparsed_files = resparse(input_sparse_file, max_download_size).unwrap();
1266        assert_eq!(2, resparsed_files.len());
1267
1268        assert_eq!(3, resparsed_files[0].chunks.len());
1269        assert_eq!(Chunk::Raw { start: 0, size: 4096 }, resparsed_files[0].chunks[0]);
1270        assert_eq!(Chunk::Fill { start: 4096, size: 4096, value: 2 }, resparsed_files[0].chunks[1]);
1271        assert_eq!(Chunk::DontCare { start: 8192, size: 4096 }, resparsed_files[0].chunks[2]);
1272
1273        assert_eq!(2, resparsed_files[1].chunks.len());
1274        assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, resparsed_files[1].chunks[0]);
1275        assert_eq!(Chunk::Raw { start: 8192, size: 4096 }, resparsed_files[1].chunks[1]);
1276    }
1277
1278    #[test]
1279    fn test_resparse_splits_large_raw_chunk() {
1280        // A single 16KB raw chunk resparsed with max download size 8KB
1281        let max_download_size = 4096 * 2;
1282        let mut chunks = Vec::<Chunk>::new();
1283        chunks.push(Chunk::Raw { start: 0, size: 16384 });
1284
1285        let input_sparse_file = SparseFileWriter::new(chunks);
1286        let resparsed_files = resparse(input_sparse_file, max_download_size).unwrap();
1287
1288        // Should split into 3 files:
1289        // File 0: Raw [0..4096], DontCare [4096..16384] (or Raw 8192 if budget allows)
1290        // Check total blocks and logical expansion
1291        let mut total_output = 0;
1292        for file in &resparsed_files {
1293            assert!(file.total_blocks() * 4096 <= 16384 + 4096);
1294            for chunk in &file.chunks {
1295                if let Chunk::Raw { size, .. } = chunk {
1296                    total_output += size;
1297                }
1298            }
1299        }
1300        assert_eq!(total_output, 16384);
1301    }
1302
1303    ////////////////////////////////////////////////////////////////////////////
1304    // Tests for add_sparse_chunk
1305
1306    #[test]
1307    fn test_add_sparse_chunk_adds_empty() {
1308        let init_vec = Vec::<Chunk>::new();
1309        let mut res = init_vec.clone();
1310        add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 });
1311        assert_eq!(0, init_vec.len());
1312        assert_ne!(init_vec, res);
1313        assert_eq!(Chunk::Fill { start: 0, size: 4096, value: 1 }, res[0]);
1314    }
1315
1316    #[test]
1317    fn test_add_sparse_chunk_fill() {
1318        // Test they merge.
1319        {
1320            let mut init_vec = Vec::<Chunk>::new();
1321            init_vec.push(Chunk::Fill { start: 0, size: 8192, value: 1 });
1322            let mut res = init_vec.clone();
1323            add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 8192, value: 1 });
1324            assert_eq!(1, res.len());
1325            assert_eq!(Chunk::Fill { start: 0, size: 16384, value: 1 }, res[0]);
1326        }
1327
1328        // Test don't merge on different value.
1329        {
1330            let mut init_vec = Vec::<Chunk>::new();
1331            init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 1 });
1332            let mut res = init_vec.clone();
1333            add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 2 });
1334            assert_ne!(res, init_vec);
1335            assert_eq!(2, res.len());
1336            assert_eq!(
1337                res,
1338                [
1339                    Chunk::Fill { start: 0, size: 4096, value: 1 },
1340                    Chunk::Fill { start: 0, size: 4096, value: 2 }
1341                ]
1342            );
1343        }
1344
1345        // Test don't merge on different type.
1346        {
1347            let mut init_vec = Vec::<Chunk>::new();
1348            init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 2 });
1349            let mut res = init_vec.clone();
1350            add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 });
1351            assert_ne!(res, init_vec);
1352            assert_eq!(2, res.len());
1353            assert_eq!(
1354                res,
1355                [
1356                    Chunk::Fill { start: 0, size: 4096, value: 2 },
1357                    Chunk::DontCare { start: 0, size: 4096 }
1358                ]
1359            );
1360        }
1361
1362        // Test don't merge when too large.
1363        {
1364            let mut init_vec = Vec::<Chunk>::new();
1365            init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 1 });
1366            let mut res = init_vec.clone();
1367            add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: u64::MAX - 4095, value: 1 });
1368            assert_ne!(res, init_vec);
1369            assert_eq!(2, res.len());
1370            assert_eq!(
1371                res,
1372                [
1373                    Chunk::Fill { start: 0, size: 4096, value: 1 },
1374                    Chunk::Fill { start: 0, size: u64::MAX - 4095, value: 1 }
1375                ]
1376            );
1377        }
1378    }
1379
1380    #[test]
1381    fn test_add_sparse_chunk_dont_care() {
1382        // Test they merge.
1383        {
1384            let mut init_vec = Vec::<Chunk>::new();
1385            init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
1386            let mut res = init_vec.clone();
1387            add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 });
1388            assert_eq!(1, res.len());
1389            assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, res[0]);
1390        }
1391
1392        // Test they don't merge on different type.
1393        {
1394            let mut init_vec = Vec::<Chunk>::new();
1395            init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
1396            let mut res = init_vec.clone();
1397            add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 });
1398            assert_eq!(2, res.len());
1399            assert_eq!(
1400                res,
1401                [
1402                    Chunk::DontCare { start: 0, size: 4096 },
1403                    Chunk::Fill { start: 0, size: 4096, value: 1 }
1404                ]
1405            );
1406        }
1407
1408        // Test they don't merge when too large.
1409        {
1410            let mut init_vec = Vec::<Chunk>::new();
1411            init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
1412            let mut res = init_vec.clone();
1413            add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: u64::MAX - 4095 });
1414            assert_eq!(2, res.len());
1415            assert_eq!(
1416                res,
1417                [
1418                    Chunk::DontCare { start: 0, size: 4096 },
1419                    Chunk::DontCare { start: 0, size: u64::MAX - 4095 }
1420                ]
1421            );
1422        }
1423    }
1424
1425    #[test]
1426    fn test_add_sparse_chunk_raw() {
1427        // Test they merge.
1428        {
1429            let mut init_vec = Vec::<Chunk>::new();
1430            init_vec.push(Chunk::Raw { start: 0, size: 12288 });
1431            let mut res = init_vec.clone();
1432            add_sparse_chunk(&mut res, Chunk::Raw { start: 0, size: 16384 });
1433            assert_eq!(1, res.len());
1434            assert_eq!(Chunk::Raw { start: 0, size: 28672 }, res[0]);
1435        }
1436
1437        // Test they don't merge on different type.
1438        {
1439            let mut init_vec = Vec::<Chunk>::new();
1440            init_vec.push(Chunk::Raw { start: 0, size: 12288 });
1441            let mut res = init_vec.clone();
1442            add_sparse_chunk(&mut res, Chunk::Fill { start: 3, size: 8192, value: 1 });
1443            assert_eq!(2, res.len());
1444            assert_eq!(
1445                res,
1446                [
1447                    Chunk::Raw { start: 0, size: 12288 },
1448                    Chunk::Fill { start: 3, size: 8192, value: 1 }
1449                ]
1450            );
1451        }
1452
1453        // Test they don't merge when too large.
1454        {
1455            let mut init_vec = Vec::<Chunk>::new();
1456            init_vec.push(Chunk::Raw { start: 0, size: 4096 });
1457            let mut res = init_vec.clone();
1458            add_sparse_chunk(&mut res, Chunk::Raw { start: 0, size: u64::MAX - 4095 });
1459            assert_eq!(2, res.len());
1460            assert_eq!(
1461                res,
1462                [
1463                    Chunk::Raw { start: 0, size: 4096 },
1464                    Chunk::Raw { start: 0, size: u64::MAX - 4095 }
1465                ]
1466            );
1467        }
1468    }
1469
1470    #[test]
1471    fn test_add_sparse_chunk_crc32() {
1472        // Test they don't merge on same type (Crc32 is special).
1473        {
1474            let mut init_vec = Vec::<Chunk>::new();
1475            init_vec.push(Chunk::Crc32 { checksum: 1234 });
1476            let mut res = init_vec.clone();
1477            add_sparse_chunk(&mut res, Chunk::Crc32 { checksum: 2345 });
1478            assert_eq!(2, res.len());
1479            assert_eq!(res, [Chunk::Crc32 { checksum: 1234 }, Chunk::Crc32 { checksum: 2345 }]);
1480        }
1481
1482        // Test they don't merge on different type.
1483        {
1484            let mut init_vec = Vec::<Chunk>::new();
1485            init_vec.push(Chunk::Crc32 { checksum: 1234 });
1486            let mut res = init_vec.clone();
1487            add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 });
1488            assert_eq!(2, res.len());
1489            assert_eq!(
1490                res,
1491                [Chunk::Crc32 { checksum: 1234 }, Chunk::Fill { start: 0, size: 4096, value: 1 }]
1492            );
1493        }
1494    }
1495
1496    ////////////////////////////////////////////////////////////////////////////
1497    // Integration
1498    //
1499
1500    #[test]
1501    fn test_roundtrip() {
1502        let tmpdir = TempDir::new().unwrap();
1503
1504        // Generate a large temporary file
1505        let (mut file, _temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1506        let mut rng: SmallRng = rand::make_rng();
1507        let mut buf = Vec::<u8>::new();
1508        buf.resize(1 * 4096, 0);
1509        rng.fill_bytes(&mut buf);
1510        file.write_all(&buf).unwrap();
1511        file.flush().unwrap();
1512        file.seek(SeekFrom::Start(0)).unwrap();
1513        let content_size = buf.len();
1514
1515        // build a sparse file
1516        let mut sparse_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
1517        SparseImageBuilder::new()
1518            .add_source(DataSource::Buffer(Box::new([0xffu8; 8192])))
1519            .add_source(DataSource::Reader { reader: Box::new(file), size: content_size as u64 })
1520            .add_source(DataSource::Fill(0xaaaa_aaaau32, 1024))
1521            .add_source(DataSource::Skip(16384))
1522            .build(&mut sparse_file)
1523            .expect("Build sparse image failed");
1524        sparse_file.seek(SeekFrom::Start(0)).unwrap();
1525
1526        let mut orig_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
1527        unsparse(&mut sparse_file, &mut orig_file).expect("unsparse failed");
1528        orig_file.seek(SeekFrom::Start(0)).unwrap();
1529
1530        let mut unsparsed_bytes = vec![];
1531        orig_file.read_to_end(&mut unsparsed_bytes).expect("Failed to read unsparsed image");
1532        assert_eq!(unsparsed_bytes.len(), 8192 + 20480 + content_size);
1533        assert_eq!(&unsparsed_bytes[..8192], &[0xffu8; 8192]);
1534        assert_eq!(&unsparsed_bytes[8192..8192 + content_size], &buf[..]);
1535        assert_eq!(&unsparsed_bytes[8192 + content_size..12288 + content_size], &[0xaau8; 4096]);
1536        assert_eq!(&unsparsed_bytes[12288 + content_size..], &[0u8; 16384]);
1537    }
1538
1539    #[test]
1540    /// test_with_simg2img is a "round trip" test that does the following
1541    ///
1542    /// 1. Generates a pseudorandom temporary file
1543    /// 2. Builds sparse files out of it
1544    /// 3. Uses the android tool simg2img to take the sparse files and generate
1545    ///    the "original" image file out of them.
1546    /// 4. Asserts the originally created file and the one created by simg2img
1547    ///    have binary equivalent contents.
1548    ///
1549    /// This gives us a reasonable expectation of correctness given that the
1550    /// Android-provided sparse tools are able to interpret our sparse images.
1551    #[cfg(target_os = "linux")]
1552    fn test_with_simg2img() {
1553        let simg2img_path = Path::new("./host_x64/test_data/storage/sparse/simg2img");
1554        assert!(
1555            Path::exists(simg2img_path),
1556            "simg2img binary must exist at {}",
1557            simg2img_path.display()
1558        );
1559
1560        let tmpdir = TempDir::new().unwrap();
1561
1562        // Generate a large temporary file
1563        let (mut file, temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1564        let mut rng: SmallRng = rand::make_rng();
1565        let mut buf = Vec::<u8>::new();
1566        // Dont want it to neatly fit a block size
1567        buf.resize(50 * 4096 + 1244, 0);
1568        rng.fill_bytes(&mut buf);
1569        file.write_all(&buf).unwrap();
1570        file.flush().unwrap();
1571        file.seek(SeekFrom::Start(0)).unwrap();
1572
1573        // build a sparse file
1574        let files = build_sparse_files(
1575            "test",
1576            temp_path.to_path_buf().to_str().expect("Should succeed"),
1577            tmpdir.path(),
1578            4096 * 2,
1579        )
1580        .unwrap();
1581
1582        let mut simg2img_output = tmpdir.path().to_path_buf();
1583        simg2img_output.push("output");
1584
1585        let mut simg2img = Command::new(simg2img_path)
1586            .args(&files[..])
1587            .arg(&simg2img_output)
1588            .stdout(Stdio::piped())
1589            .stderr(Stdio::piped())
1590            .spawn()
1591            .expect("Failed to spawn simg2img");
1592        let res = simg2img.wait().expect("simg2img did was not running");
1593        assert!(res.success(), "simg2img did not succeed");
1594        let mut simg2img_stdout = simg2img.stdout.take().expect("Get stdout from simg2img");
1595        let mut simg2img_stderr = simg2img.stderr.take().expect("Get stderr from simg2img");
1596
1597        let mut stdout = String::new();
1598        simg2img_stdout.read_to_string(&mut stdout).expect("Reading simg2img stdout");
1599        assert_eq!(stdout, "");
1600
1601        let mut stderr = String::new();
1602        simg2img_stderr.read_to_string(&mut stderr).expect("Reading simg2img stderr");
1603        assert_eq!(stderr, "");
1604
1605        let simg2img_output_bytes =
1606            std::fs::read(simg2img_output).expect("Failed to read simg2img output");
1607
1608        assert_eq!(
1609            buf,
1610            simg2img_output_bytes[0..buf.len()],
1611            "Output from simg2img should match our generated file"
1612        );
1613
1614        assert_eq!(
1615            simg2img_output_bytes[buf.len()..],
1616            vec![0u8; simg2img_output_bytes.len() - buf.len()],
1617            "The remainder of our simg2img_output_bytes should be 0"
1618        );
1619    }
1620
1621    #[test]
1622    #[cfg(target_os = "linux")]
1623    fn test_resparse_from_sparse() {
1624        use crate::reader::SparseReader;
1625        use crate::resparse_sparse_img;
1626
1627        let simg2img_path = Path::new("./host_x64/test_data/storage/sparse/simg2img");
1628        assert!(
1629            Path::exists(simg2img_path),
1630            "simg2img binary must exist at {}",
1631            simg2img_path.display()
1632        );
1633
1634        let tmpdir = TempDir::new().unwrap();
1635
1636        // Generate a large temporary file
1637        let (mut file, _temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1638        let mut rng: SmallRng = rand::make_rng();
1639        let mut buf = Vec::<u8>::new();
1640        buf.resize(10 * 4096, 0);
1641        rng.fill_bytes(&mut buf);
1642        file.write_all(&buf).unwrap();
1643        file.flush().unwrap();
1644        file.seek(SeekFrom::Start(0)).unwrap();
1645        let content_size = buf.len();
1646
1647        // build a sparse file
1648        let sparse_file_tmp = NamedTempFile::new_in(&tmpdir).unwrap();
1649        let mut sparse_file = sparse_file_tmp.into_file();
1650        SparseImageBuilder::new()
1651            .add_source(DataSource::Buffer(Box::new([0xffu8; 4096 * 2])))
1652            .add_source(DataSource::Reader { reader: Box::new(file), size: content_size as u64 })
1653            .add_source(DataSource::Fill(0xaaaa_aaaau32, 1024))
1654            .add_source(DataSource::Skip(16384))
1655            .build(&mut sparse_file)
1656            .expect("Build sparse image failed");
1657        sparse_file.seek(SeekFrom::Start(0)).unwrap();
1658
1659        let mut reader = SparseReader::new(sparse_file).expect("create reader");
1660
1661        let files = resparse_sparse_img(&mut reader, tmpdir.path(), 4096 * 3).unwrap();
1662
1663        // Re build the image from the sparse files
1664        let mut simg2img_output_sparsed = tmpdir.path().to_path_buf();
1665        simg2img_output_sparsed.push("output_sparsed");
1666
1667        let mut simg2img_sparsed = Command::new(simg2img_path)
1668            .args(&files[..])
1669            .arg(&simg2img_output_sparsed)
1670            .stdout(Stdio::piped())
1671            .stderr(Stdio::piped())
1672            .spawn()
1673            .expect("Failed to spawn simg2img");
1674        let res = simg2img_sparsed.wait().expect("simg2img did was not running");
1675        assert!(res.success(), "simg2img did not succeed");
1676        let mut simg2img_stdout = simg2img_sparsed.stdout.take().expect("Get stdout from simg2img");
1677        let mut simg2img_stderr = simg2img_sparsed.stderr.take().expect("Get stderr from simg2img");
1678
1679        let mut stdout = String::new();
1680        simg2img_stdout.read_to_string(&mut stdout).expect("Reading simg2img stdout");
1681        assert_eq!(stdout, "");
1682
1683        let mut stderr = String::new();
1684        simg2img_stderr.read_to_string(&mut stderr).expect("Reading simg2img stderr");
1685        assert_eq!(stderr, "");
1686    }
1687
1688    #[test]
1689    fn test_find_fill_value() {
1690        assert_eq!(super::find_fill_value(&[]), None);
1691        assert_eq!(super::find_fill_value(&[1, 2, 3]), None);
1692        assert_eq!(super::find_fill_value(&[1, 2, 3, 4, 5]), None);
1693
1694        let mut buf = [0u8; 4096];
1695        assert_eq!(super::find_fill_value(&buf), Some(0));
1696
1697        buf.fill(0xaa);
1698        assert_eq!(super::find_fill_value(&buf), Some(0xaaaa_aaaa));
1699
1700        for chunk in buf.chunks_exact_mut(4) {
1701            chunk.copy_from_slice(&0x1234_5678u32.to_le_bytes());
1702        }
1703        assert_eq!(super::find_fill_value(&buf), Some(0x1234_5678));
1704
1705        // Mismatch at start
1706        buf[0] = 0x00;
1707        assert_eq!(super::find_fill_value(&buf), None);
1708
1709        // Restore and mismatch in middle
1710        buf[0] = 0x78;
1711        buf[2048] = 0x00;
1712        assert_eq!(super::find_fill_value(&buf), None);
1713
1714        // Restore and mismatch at end
1715        buf[2048] = 0x78;
1716        buf[4095] = 0x00;
1717        assert_eq!(super::find_fill_value(&buf), None);
1718    }
1719
1720    #[test]
1721    fn test_sparse_slice_reader_matches_write() {
1722        let mut source_data = Vec::<u8>::new();
1723        source_data.resize(4096 * 4, 0);
1724        let mut rng: SmallRng = rand::make_rng();
1725        rng.fill_bytes(&mut source_data);
1726
1727        let mut chunks = Vec::<Chunk>::new();
1728        chunks.push(Chunk::Raw { start: 0, size: 4096 * 2 });
1729        chunks.push(Chunk::Fill { start: 4096 * 2, size: 4096, value: 0x1234_5678 });
1730        chunks.push(Chunk::DontCare { start: 4096 * 3, size: 4096 });
1731        chunks.push(Chunk::Raw { start: 4096 * 3, size: 4096 });
1732
1733        let writer = SparseFileWriter::new(chunks);
1734
1735        // 1. Write via SparseFileWriter::write
1736        let mut written_bytes = Cursor::new(Vec::<u8>::new());
1737        let mut source_cursor = Cursor::new(source_data.clone());
1738        writer.write(&mut source_cursor, &mut written_bytes).unwrap();
1739        let expected = written_bytes.into_inner();
1740
1741        // 2. Read via SparseSliceReader
1742        let mut slice_source = Cursor::new(source_data.clone());
1743        let mut slice_reader = writer.slice_reader(&mut slice_source).unwrap();
1744        let mut read_bytes = Vec::<u8>::new();
1745        slice_reader.read_to_end(&mut read_bytes).unwrap();
1746
1747        assert_eq!(expected, read_bytes);
1748        assert_eq!(read_bytes.len() as u64, writer.file_size());
1749    }
1750}