1#![doc = include_str!("../README.md")]
2
3pub mod split;
5
6use bytes::{Buf, BufMut};
7use log::trace;
8use strum::FromRepr;
9use thiserror::Error;
10
11pub const FILE_HEADER_BYTES_LEN: usize = 28;
13pub const CHUNK_HEADER_BYTES_LEN: usize = 12;
15pub const HEADER_MAGIC: u32 = 0xed26ff3a;
17pub const DEFAULT_BLOCKSIZE: u32 = 4096;
18
19#[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
32pub type FileHeaderBytes = [u8; FILE_HEADER_BYTES_LEN];
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FileHeader {
37 pub block_size: u32,
39 pub blocks: u32,
41 pub chunks: u32,
43 pub checksum: u32,
45}
46
47impl FileHeader {
48 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 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 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#[derive(Copy, Clone, Debug, FromRepr, Eq, PartialEq)]
120pub enum ChunkType {
121 Raw = 0xcac1,
124 Fill = 0xcac2,
126 DontCare = 0xcac3,
129 Crc32 = 0xcac4,
131}
132
133pub type ChunkHeaderBytes = [u8; CHUNK_HEADER_BYTES_LEN];
135
136#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct ChunkHeader {
139 pub chunk_type: ChunkType,
141 pub chunk_size: u32,
143 pub total_size: u32,
145}
146
147impl ChunkHeader {
148 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 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 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 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 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 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 pub fn out_size(&self, header: &FileHeader) -> usize {
213 self.chunk_size as usize * header.block_size as usize
214 }
215
216 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}