android_sparse_image/
lib.rs

1#![doc = include_str!("../README.md")]
2
3/// Helpers to split an image into multiple smaller ones
4pub mod split;
5
6use bytes::{Buf, BufMut};
7use log::trace;
8use strum::FromRepr;
9use thiserror::Error;
10
11/// Length of the file header in bytes
12pub const FILE_HEADER_BYTES_LEN: usize = 28;
13/// Length of the chunk header in bytes
14pub const CHUNK_HEADER_BYTES_LEN: usize = 12;
15/// File magic - This are the first 4 bytes in little-endian
16pub const HEADER_MAGIC: u32 = 0xed26ff3a;
17pub const DEFAULT_BLOCKSIZE: u32 = 4096;
18
19/// Byte parsing errors
20#[derive(Clone, Debug, Error)]
21pub enum ParseError {
22    #[error("Header has an unknown magic value")]
23    UnknownMagic,
24    #[error("Header has an unknown version")]
25    UnknownVersion,
26    #[error("Header has an unexpected header or chunk size")]
27    UnexpectedSize,
28    #[error("Header has an unknown chunk type")]
29    UnknownChunkType,
30}
31
32/// Byte array which fits a file header
33pub type FileHeaderBytes = [u8; FILE_HEADER_BYTES_LEN];
34/// Global file header
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FileHeader {
37    /// Block size in bytes (should be multiple of 4)
38    pub block_size: u32,
39    /// Number of blocks in the expanded image
40    pub blocks: u32,
41    /// Number of chunks in the sparse image
42    pub chunks: u32,
43    /// Optional CRC32 Checksum
44    pub checksum: u32,
45}
46
47impl FileHeader {
48    /// Create new FileHeader from a raw header
49    pub fn from_bytes(bytes: &FileHeaderBytes) -> Result<FileHeader, ParseError> {
50        let mut bytes = &bytes[..];
51
52        let magic = bytes.get_u32_le();
53        if magic != HEADER_MAGIC {
54            trace!("Unrecognized header magic: {:x}", magic);
55            return Err(ParseError::UnknownMagic);
56        }
57
58        let major = bytes.get_u16_le();
59        if major != 0x1 {
60            trace!("Unrecognized major versions: {:x}", major);
61            return Err(ParseError::UnknownVersion);
62        }
63
64        let minor = bytes.get_u16_le();
65        if minor != 0x0 {
66            trace!("Unrecognized minor versions: {:x}", minor);
67            return Err(ParseError::UnknownVersion);
68        }
69
70        let header_len = bytes.get_u16_le();
71        if FILE_HEADER_BYTES_LEN != header_len.into() {
72            trace!("Unexpected header size: {}", header_len);
73            return Err(ParseError::UnexpectedSize);
74        }
75
76        let chunk_header_len = bytes.get_u16_le();
77        if CHUNK_HEADER_BYTES_LEN != chunk_header_len.into() {
78            trace!("Unexpected chunk header size: {}", chunk_header_len);
79            return Err(ParseError::UnexpectedSize);
80        }
81
82        let block_size = bytes.get_u32_le();
83        let blocks = bytes.get_u32_le();
84        let chunks = bytes.get_u32_le();
85        let checksum = bytes.get_u32_le();
86
87        Ok(FileHeader {
88            block_size,
89            blocks,
90            chunks,
91            checksum,
92        })
93    }
94
95    /// Convert into a raw header
96    pub fn to_bytes(&self) -> FileHeaderBytes {
97        let mut bytes = [0; FILE_HEADER_BYTES_LEN];
98        let mut w = &mut bytes[..];
99        w.put_u32_le(HEADER_MAGIC);
100        // Version 1.0
101        w.put_u16_le(0x1);
102        w.put_u16_le(0x0);
103        w.put_u16_le(FILE_HEADER_BYTES_LEN as u16);
104        w.put_u16_le(CHUNK_HEADER_BYTES_LEN as u16);
105        w.put_u32_le(self.block_size);
106        w.put_u32_le(self.blocks);
107        w.put_u32_le(self.chunks);
108        w.put_u32_le(self.checksum);
109
110        bytes
111    }
112
113    pub fn total_size(&self) -> usize {
114        self.blocks as usize * self.block_size as usize
115    }
116}
117
118/// Type of a chunk
119#[derive(Copy, Clone, Debug, FromRepr, Eq, PartialEq)]
120pub enum ChunkType {
121    /// Chunk header is followed by raw content for [ChunkHeader::out_size] bytes; Should be copied
122    /// to the output
123    Raw = 0xcac1,
124    /// Chunk header is followed by 4 bytes; which should be used to fill the output
125    Fill = 0xcac2,
126    /// No data after the chunk; The next [ChunkHeader::out_size] bytes can be filled with any
127    /// content
128    DontCare = 0xcac3,
129    /// Chunk header is followed by 4 bytes, which is a crc32 checksum
130    Crc32 = 0xcac4,
131}
132
133/// Byte array which fits a chunk header
134pub type ChunkHeaderBytes = [u8; CHUNK_HEADER_BYTES_LEN];
135
136/// Header of a chunk
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct ChunkHeader {
139    /// The type of the chunk
140    pub chunk_type: ChunkType,
141    /// Output size of the chunk in blocksize
142    pub chunk_size: u32,
143    /// Size of the chunk in the sparse image
144    pub total_size: u32,
145}
146
147impl ChunkHeader {
148    /// Create a don't care header for a given length in blocks
149    pub fn new_dontcare(blocks: u32) -> Self {
150        ChunkHeader {
151            chunk_type: ChunkType::DontCare,
152            total_size: CHUNK_HEADER_BYTES_LEN as u32,
153            chunk_size: blocks,
154        }
155    }
156
157    /// Create a new raw header for a given amount in blocks for block_size
158    ///
159    /// The actual data should follow this header
160    pub fn new_raw(blocks: u32, block_size: u32) -> Self {
161        ChunkHeader {
162            chunk_type: ChunkType::Raw,
163            chunk_size: blocks,
164            total_size: (CHUNK_HEADER_BYTES_LEN as u32)
165                .saturating_add(blocks.saturating_mul(block_size)),
166        }
167    }
168
169    /// Create a new fill header for a given amount of blocks to be filled
170    ///
171    /// The header should be followed by 4 bytes indicate the data to fill with
172    pub fn new_fill(blocks: u32) -> Self {
173        ChunkHeader {
174            chunk_type: ChunkType::Fill,
175            chunk_size: blocks,
176            total_size: CHUNK_HEADER_BYTES_LEN as u32 + 4,
177        }
178    }
179
180    /// Create new ChunkHeader from a raw header
181    pub fn from_bytes(bytes: &ChunkHeaderBytes) -> Result<ChunkHeader, ParseError> {
182        let mut bytes = &bytes[..];
183        let chunk_type = bytes.get_u16_le();
184        let Some(chunk_type) = ChunkType::from_repr(chunk_type.into()) else {
185            trace!("Unknown chunk type: {}", chunk_type);
186            return Err(ParseError::UnknownChunkType);
187        };
188        // reserved
189        bytes.advance(2);
190        let chunk_size = bytes.get_u32_le();
191        let total_size = bytes.get_u32_le();
192
193        Ok(ChunkHeader {
194            chunk_type,
195            chunk_size,
196            total_size,
197        })
198    }
199
200    /// Convert into a raw header
201    pub fn to_bytes(&self) -> ChunkHeaderBytes {
202        let mut bytes = [0; CHUNK_HEADER_BYTES_LEN];
203        let mut w = &mut bytes[..];
204        w.put_u16_le(self.chunk_type as u16);
205        w.put_u16_le(0x0);
206        w.put_u32_le(self.chunk_size);
207        w.put_u32_le(self.total_size);
208        bytes
209    }
210
211    /// Resulting size of this chunk in the output
212    pub fn out_size(&self, header: &FileHeader) -> usize {
213        self.chunk_size as usize * header.block_size as usize
214    }
215
216    /// Data bytes after the header
217    pub fn data_size(&self) -> usize {
218        (self.total_size as usize).saturating_sub(CHUNK_HEADER_BYTES_LEN)
219    }
220}
221
222#[cfg(test)]
223mod test {
224    use super::*;
225
226    #[test]
227    fn file_header_parse() {
228        let data = [
229            0x3au8, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10,
230            0x00, 0x00, 0x77, 0x39, 0x14, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xcc,
231        ];
232
233        let h = FileHeader::from_bytes(&data).unwrap();
234        assert_eq!(
235            h,
236            FileHeader {
237                block_size: 4096,
238                blocks: 1325431,
239                chunks: 177,
240                checksum: 0xcc0000aa,
241            }
242        );
243    }
244
245    #[test]
246    fn file_header_roundtrip() {
247        let orig = FileHeader {
248            block_size: 4096,
249            blocks: 1024,
250            chunks: 42,
251            checksum: 0xabcd,
252        };
253
254        let b = orig.to_bytes();
255        let echo = FileHeader::from_bytes(&b).unwrap();
256
257        assert_eq!(orig, echo);
258    }
259
260    #[test]
261    fn chunk_header_parse() {
262        let data = [
263            0xc3u8, 0xca, 0x0, 0x0, 0x1f, 0xf1, 0xaa, 0xbb, 0x0c, 0x00, 0x00, 0x00,
264        ];
265
266        let h = ChunkHeader::from_bytes(&data).unwrap();
267        assert_eq!(
268            h,
269            ChunkHeader {
270                chunk_type: ChunkType::DontCare,
271                chunk_size: 0xbbaaf11f,
272                total_size: CHUNK_HEADER_BYTES_LEN as u32,
273            }
274        );
275    }
276
277    #[test]
278    fn chunk_header_roundtrip() {
279        let orig = ChunkHeader {
280            chunk_type: ChunkType::Fill,
281            chunk_size: 8,
282            total_size: (CHUNK_HEADER_BYTES_LEN + 4) as u32,
283        };
284
285        let b = orig.to_bytes();
286        let echo = ChunkHeader::from_bytes(&b).unwrap();
287
288        assert_eq!(orig, echo);
289    }
290}