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")) }
180
181fn decode_zlib_resource_fork(fork: &[u8], uncompressed_size: usize) -> crate::Result<Vec<u8>> {
183 let header_size = be_u32(fork, 0)? as usize;
185 let table = header_size.checked_add(4).ok_or(ApfsError::Decmpfs(
189 "decmpfs zlib fork table offset overflow",
190 ))?;
191 let num_blocks = le_u32(fork, table)? as usize;
192 let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
193 for i in 0..num_blocks {
194 let entry = table
195 .checked_add(4)
196 .and_then(|b| b.checked_add(i.checked_mul(8)?))
197 .ok_or(ApfsError::Decmpfs(
198 "decmpfs zlib fork entry offset overflow",
199 ))?;
200 let offset = le_u32(fork, entry)? as usize;
201 let size = le_u32(fork, entry + 4)? as usize;
202 let start = table
203 .checked_add(offset)
204 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block start overflow"))?;
205 let end = start
206 .checked_add(size)
207 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block end overflow"))?;
208 let block = fork
209 .get(start..end)
210 .ok_or(ApfsError::Decmpfs("decmpfs zlib fork block out of bounds"))?;
211 out.extend_from_slice(&inflate(block)?);
212 }
213 Ok(out)
214}
215
216fn decode_chunked_resource_fork(
219 algorithm: Algorithm,
220 fork: &[u8],
221 uncompressed_size: usize,
222) -> crate::Result<Vec<u8>> {
223 let header_size = le_u32(fork, 0)? as usize;
224 let n_slots = (header_size / 4)
230 .checked_sub(1)
231 .ok_or(ApfsError::Decmpfs("decmpfs chunked fork header too small"))?;
232 let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
233 let mut src = header_size;
234 for i in 0..n_slots {
235 if out.len() >= uncompressed_size {
236 break;
237 }
238 let end = le_u32(fork, 4 + i * 4)? as usize;
239 if end < src {
240 return Err(ApfsError::Decmpfs(
241 "decmpfs chunked fork end-offset goes backward",
242 ));
243 }
244 let chunk = fork.get(src..end).ok_or(ApfsError::Decmpfs(
245 "decmpfs chunked fork chunk out of bounds",
246 ))?;
247 let chunk_uncompressed = uncompressed_size
248 .checked_sub(out.len())
249 .ok_or(ApfsError::Decmpfs("decmpfs chunked fork size underflow"))? .min(CHUNK_SIZE);
251 let decoded = match algorithm {
252 Algorithm::Lzvn => lzvn_decode(chunk, chunk_uncompressed)?,
253 Algorithm::Lzfse => lzfse_decode(chunk)?,
254 Algorithm::Uncompressed => chunk.to_vec(),
255 _ => return unreachable_algorithm(), };
259 out.extend_from_slice(&decoded);
260 src = end;
261 }
262 Ok(out)
263}
264
265const MAX_DECMPFS_CAP: usize = 1 << 30; fn inflate(data: &[u8]) -> crate::Result<Vec<u8>> {
271 let mut decoder = flate2::read::ZlibDecoder::new(data);
272 let mut out = Vec::new();
273 decoder
274 .read_to_end(&mut out)
275 .map_err(|_| ApfsError::Decmpfs("decmpfs zlib codec error"))?;
276 Ok(out)
277}
278
279fn lzvn_decode(chunk: &[u8], uncompressed_len: usize) -> crate::Result<Vec<u8>> {
282 lzvn::decode(chunk, uncompressed_len)
283 .map_err(|_| ApfsError::Decmpfs("decmpfs lzvn codec error"))
284}
285
286fn lzfse_decode(stream: &[u8]) -> crate::Result<Vec<u8>> {
288 let mut out = Vec::new();
289 lzfse_rust::decode_bytes(stream, &mut out)
290 .map_err(|_| ApfsError::Decmpfs("decmpfs lzfse codec error"))?;
291 Ok(out)
292}
293
294fn le_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
297 let end = offset
298 .checked_add(4)
299 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
300 let bytes = data
301 .get(offset..end)
302 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
303 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
304}
305
306fn be_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
307 let end = offset
308 .checked_add(4)
309 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
310 let bytes = data
311 .get(offset..end)
312 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
313 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
314}
315
316fn le_u64(data: &[u8], offset: usize) -> crate::Result<u64> {
317 let end = offset
318 .checked_add(8)
319 .ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
320 let bytes = data
321 .get(offset..end)
322 .ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
323 let mut a = [0u8; 8];
324 a.copy_from_slice(bytes);
325 Ok(u64::from_le_bytes(a))
326}