1use std::io::{Read, Seek};
26
27use forensicnomicon::decmpfs::{
28 self, Algorithm, Storage, CHUNK_SIZE, COMPRESSION_TYPE_OFFSET, HEADER_LEN, MAGIC,
29 UNCOMPRESSED_SIZE_OFFSET,
30};
31
32use crate::inode::Inode;
33use crate::volume::ApfsVolume;
34use crate::ApfsError;
35
36pub fn read_compressed<R: Read + Seek>(
46 reader: &mut R,
47 volume: &ApfsVolume,
48 inode: &Inode,
49 header: &[u8],
50 block_size: usize,
51) -> crate::Result<Vec<u8>> {
52 let compression_type = le_u32(header, COMPRESSION_TYPE_OFFSET)?;
55 let needs_fork =
56 decmpfs::classify(compression_type).is_some_and(|c| c.storage == Storage::ResourceFork);
57
58 let fork = if needs_fork {
59 crate::xattr::resource_fork(reader, volume, inode.oid, block_size)?
60 } else {
61 None
62 };
63
64 decompress_decmpfs(header, fork.as_deref())
65}
66
67pub fn decompress_decmpfs(header: &[u8], resource_fork: Option<&[u8]>) -> crate::Result<Vec<u8>> {
78 if header.len() < HEADER_LEN {
79 return Err(ApfsError::Decmpfs(
80 "decmpfs xattr shorter than 16-byte header",
81 ));
82 }
83 let magic = le_u32(header, 0)?;
84 if magic != MAGIC {
85 return Err(ApfsError::Decmpfs("decmpfs bad magic (expected 'cmpf')"));
86 }
87 let compression_type = le_u32(header, COMPRESSION_TYPE_OFFSET)?;
88 let uncompressed_size = le_u64(header, UNCOMPRESSED_SIZE_OFFSET)? as usize;
89
90 let Some(kind) = decmpfs::classify(compression_type) else {
91 return Err(match compression_type {
92 5 => ApfsError::Decmpfs("decmpfs type 5 (de-dup generation store, no payload)"),
93 _ => ApfsError::Decmpfs("decmpfs unknown compression_type"),
94 });
95 };
96 if kind.algorithm == Algorithm::LzBitmap {
97 return Err(ApfsError::Decmpfs("decmpfs LZBitmap (no public spec)"));
98 }
99
100 let out = match kind.storage {
101 Storage::Inline => {
102 let payload = header
103 .get(HEADER_LEN..)
104 .ok_or(ApfsError::Decmpfs("decmpfs inline payload truncated"))?;
105 decode_inline(kind.algorithm, payload, uncompressed_size, compression_type)?
106 }
107 Storage::ResourceFork => {
108 let fork = resource_fork.ok_or(ApfsError::Decmpfs(
109 "decmpfs resource-fork type but no fork present",
110 ))?;
111 decode_resource_fork(kind.algorithm, fork, uncompressed_size)?
112 }
113 };
114
115 if out.len() != uncompressed_size {
116 return Err(ApfsError::Decmpfs(
117 "decmpfs decoded length != uncompressed_size",
118 ));
119 }
120 Ok(out)
121}
122
123fn decode_inline(
131 algorithm: Algorithm,
132 payload: &[u8],
133 uncompressed_size: usize,
134 compression_type: u32,
135) -> crate::Result<Vec<u8>> {
136 match algorithm {
137 Algorithm::Uncompressed => match compression_type {
138 9 => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
139 _ => Ok(payload.to_vec()),
140 },
141 Algorithm::Zlib => match payload.first() {
142 Some(0xFF) => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
144 _ => inflate(payload),
145 },
146 Algorithm::Lzvn => match payload.first() {
147 Some(0x06) => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
149 _ => lzvn_decode(payload, uncompressed_size),
150 },
151 Algorithm::Lzfse => lzfse_decode(payload),
152 _ => unreachable_algorithm(), }
154}
155
156fn decode_resource_fork(
158 algorithm: Algorithm,
159 fork: &[u8],
160 uncompressed_size: usize,
161) -> crate::Result<Vec<u8>> {
162 match algorithm {
163 Algorithm::Zlib => decode_zlib_resource_fork(fork, uncompressed_size),
164 Algorithm::Lzvn | Algorithm::Lzfse | Algorithm::Uncompressed => {
165 decode_chunked_resource_fork(algorithm, fork, uncompressed_size)
166 }
167 _ => unreachable_algorithm(), }
169}
170
171#[inline]
176fn unreachable_algorithm() -> crate::Result<Vec<u8>> {
177 Err(ApfsError::Decmpfs("decmpfs unsupported algorithm")) }
179
180fn decode_zlib_resource_fork(fork: &[u8], uncompressed_size: usize) -> crate::Result<Vec<u8>> {
182 let header_size = be_u32(fork, 0)? as usize;
184 let table = header_size.checked_add(4).ok_or(ApfsError::Decmpfs(
188 "decmpfs zlib fork table offset overflow",
189 ))?;
190 let num_blocks = le_u32(fork, table)? as usize;
191 let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
192 for i in 0..num_blocks {
193 let entry = table
194 .checked_add(4)
195 .and_then(|b| b.checked_add(i.checked_mul(8)?))
196 .ok_or(ApfsError::Decmpfs(
197 "decmpfs zlib fork entry offset overflow",
198 ))?;
199 let offset = le_u32(fork, entry)? as usize;
200 let size = le_u32(fork, entry + 4)? as usize;
201 let start = table
202 .checked_add(offset)
203 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block start overflow"))?;
204 let end = start
205 .checked_add(size)
206 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block end overflow"))?;
207 let block = fork
208 .get(start..end)
209 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block out of bounds"))?;
210 out.extend_from_slice(&inflate(block)?);
211 }
212 Ok(out)
213}
214
215fn decode_chunked_resource_fork(
218 algorithm: Algorithm,
219 fork: &[u8],
220 uncompressed_size: usize,
221) -> crate::Result<Vec<u8>> {
222 let header_size = le_u32(fork, 0)? as usize;
223 let n_slots = (header_size / 4)
229 .checked_sub(1)
230 .ok_or(ApfsError::Decmpfs("decmpfs chunked fork header too small"))?;
231 let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
232 let mut src = header_size;
233 for i in 0..n_slots {
234 if out.len() >= uncompressed_size {
235 break;
236 }
237 let end = le_u32(fork, 4 + i * 4)? as usize;
238 if end < src {
239 return Err(ApfsError::Decmpfs(
240 "decmpfs chunked fork end-offset goes backward",
241 ));
242 }
243 let chunk = fork.get(src..end).ok_or(ApfsError::Decmpfs(
244 "decmpfs chunked fork chunk out of bounds",
245 ))?;
246 let chunk_uncompressed = uncompressed_size
247 .checked_sub(out.len())
248 .ok_or(ApfsError::Decmpfs("decmpfs chunked fork size underflow"))? .min(CHUNK_SIZE);
250 let decoded = match algorithm {
251 Algorithm::Lzvn => lzvn_decode(chunk, chunk_uncompressed)?,
252 Algorithm::Lzfse => lzfse_decode(chunk)?,
253 Algorithm::Uncompressed => chunk.to_vec(),
254 _ => return unreachable_algorithm(), };
258 out.extend_from_slice(&decoded);
259 src = end;
260 }
261 Ok(out)
262}
263
264const MAX_DECMPFS_CAP: usize = 1 << 30; fn inflate(data: &[u8]) -> crate::Result<Vec<u8>> {
270 let mut decoder = flate2::read::ZlibDecoder::new(data);
271 let mut out = Vec::new();
272 decoder
273 .read_to_end(&mut out)
274 .map_err(|_| ApfsError::Decmpfs("decmpfs zlib codec error"))?;
275 Ok(out)
276}
277
278fn lzvn_decode(chunk: &[u8], uncompressed_len: usize) -> crate::Result<Vec<u8>> {
281 lzvn::decode(chunk, uncompressed_len)
282 .map_err(|_| ApfsError::Decmpfs("decmpfs lzvn codec error"))
283}
284
285fn lzfse_decode(stream: &[u8]) -> crate::Result<Vec<u8>> {
287 let mut out = Vec::new();
288 lzfse_rust::decode_bytes(stream, &mut out)
289 .map_err(|_| ApfsError::Decmpfs("decmpfs lzfse codec error"))?;
290 Ok(out)
291}
292
293fn le_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
296 let end = offset
297 .checked_add(4)
298 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
299 let bytes = data
300 .get(offset..end)
301 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
302 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
303}
304
305fn be_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
306 let end = offset
307 .checked_add(4)
308 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
309 let bytes = data
310 .get(offset..end)
311 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
312 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
313}
314
315fn le_u64(data: &[u8], offset: usize) -> crate::Result<u64> {
316 let end = offset
317 .checked_add(8)
318 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
319 let bytes = data
320 .get(offset..end)
321 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
322 let mut a = [0u8; 8];
323 a.copy_from_slice(bytes);
324 Ok(u64::from_le_bytes(a))
325}