use std::io::{Read, Seek};
use forensicnomicon::decmpfs::{
self, Algorithm, Storage, CHUNK_SIZE, COMPRESSION_TYPE_OFFSET, HEADER_LEN, MAGIC,
UNCOMPRESSED_SIZE_OFFSET,
};
use crate::inode::Inode;
use crate::volume::ApfsVolume;
use crate::ApfsError;
pub fn read_compressed<R: Read + Seek>(
reader: &mut R,
volume: &ApfsVolume,
inode: &Inode,
header: &[u8],
block_size: usize,
) -> crate::Result<Vec<u8>> {
let compression_type = le_u32(header, COMPRESSION_TYPE_OFFSET)?;
let needs_fork =
decmpfs::classify(compression_type).is_some_and(|c| c.storage == Storage::ResourceFork);
let fork = if needs_fork {
crate::xattr::resource_fork(reader, volume, inode.oid, block_size)?
} else {
None
};
decompress_decmpfs(header, fork.as_deref())
}
pub fn decompress_decmpfs(header: &[u8], resource_fork: Option<&[u8]>) -> crate::Result<Vec<u8>> {
if header.len() < HEADER_LEN {
return Err(ApfsError::Decmpfs(
"decmpfs xattr shorter than 16-byte header",
));
}
let magic = le_u32(header, 0)?;
if magic != MAGIC {
return Err(ApfsError::Decmpfs("decmpfs bad magic (expected 'cmpf')"));
}
let compression_type = le_u32(header, COMPRESSION_TYPE_OFFSET)?;
let uncompressed_size = le_u64(header, UNCOMPRESSED_SIZE_OFFSET)? as usize;
let Some(kind) = decmpfs::classify(compression_type) else {
return Err(match compression_type {
5 => ApfsError::Decmpfs("decmpfs type 5 (de-dup generation store, no payload)"),
_ => ApfsError::Decmpfs("decmpfs unknown compression_type"),
});
};
if kind.algorithm == Algorithm::LzBitmap {
return Err(ApfsError::Decmpfs("decmpfs LZBitmap (no public spec)"));
}
let out = match kind.storage {
Storage::Inline => {
let payload = header
.get(HEADER_LEN..)
.ok_or(ApfsError::Decmpfs("decmpfs inline payload truncated"))?;
decode_inline(kind.algorithm, payload, uncompressed_size, compression_type)?
}
Storage::ResourceFork => {
let fork = resource_fork.ok_or(ApfsError::Decmpfs(
"decmpfs resource-fork type but no fork present",
))?;
decode_resource_fork(kind.algorithm, fork, uncompressed_size)?
}
};
if out.len() != uncompressed_size {
return Err(ApfsError::Decmpfs(
"decmpfs decoded length != uncompressed_size",
));
}
Ok(out)
}
fn decode_inline(
algorithm: Algorithm,
payload: &[u8],
uncompressed_size: usize,
compression_type: u32,
) -> crate::Result<Vec<u8>> {
match algorithm {
Algorithm::Uncompressed => match compression_type {
9 => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
_ => Ok(payload.to_vec()),
},
Algorithm::Zlib => match payload.first() {
Some(0xFF) => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
_ => inflate(payload),
},
Algorithm::Lzvn => match payload.first() {
Some(0x06) => Ok(payload.get(1..).unwrap_or(&[]).to_vec()),
_ => lzvn_decode(payload, uncompressed_size),
},
Algorithm::Lzfse => lzfse_decode(payload),
_ => unreachable_algorithm(), }
}
fn decode_resource_fork(
algorithm: Algorithm,
fork: &[u8],
uncompressed_size: usize,
) -> crate::Result<Vec<u8>> {
match algorithm {
Algorithm::Zlib => decode_zlib_resource_fork(fork, uncompressed_size),
Algorithm::Lzvn | Algorithm::Lzfse | Algorithm::Uncompressed => {
decode_chunked_resource_fork(algorithm, fork, uncompressed_size)
}
_ => unreachable_algorithm(), }
}
#[inline]
fn unreachable_algorithm() -> crate::Result<Vec<u8>> {
Err(ApfsError::Decmpfs("decmpfs unsupported algorithm")) }
fn decode_zlib_resource_fork(fork: &[u8], uncompressed_size: usize) -> crate::Result<Vec<u8>> {
let header_size = be_u32(fork, 0)? as usize;
let table = header_size.checked_add(4).ok_or(ApfsError::Decmpfs(
"decmpfs zlib fork table offset overflow",
))?;
let num_blocks = le_u32(fork, table)? as usize;
let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
for i in 0..num_blocks {
let entry = table
.checked_add(4)
.and_then(|b| b.checked_add(i.checked_mul(8)?))
.ok_or(ApfsError::Decmpfs(
"decmpfs zlib fork entry offset overflow",
))?;
let offset = le_u32(fork, entry)? as usize;
let size = le_u32(fork, entry + 4)? as usize;
let start = table
.checked_add(offset)
.ok_or(ApfsError::Decmpfs("decmpfs zlib fork block start overflow"))?;
let end = start
.checked_add(size)
.ok_or(ApfsError::Decmpfs("decmpfs zlib fork block end overflow"))?;
let block = fork
.get(start..end)
.ok_or(ApfsError::Decmpfs("decmpfs zlib fork block out of bounds"))?;
out.extend_from_slice(&inflate(block)?);
}
Ok(out)
}
fn decode_chunked_resource_fork(
algorithm: Algorithm,
fork: &[u8],
uncompressed_size: usize,
) -> crate::Result<Vec<u8>> {
let header_size = le_u32(fork, 0)? as usize;
let n_slots = (header_size / 4)
.checked_sub(1)
.ok_or(ApfsError::Decmpfs("decmpfs chunked fork header too small"))?;
let mut out = Vec::with_capacity(uncompressed_size.min(MAX_DECMPFS_CAP));
let mut src = header_size;
for i in 0..n_slots {
if out.len() >= uncompressed_size {
break;
}
let end = le_u32(fork, 4 + i * 4)? as usize;
if end < src {
return Err(ApfsError::Decmpfs(
"decmpfs chunked fork end-offset goes backward",
));
}
let chunk = fork.get(src..end).ok_or(ApfsError::Decmpfs(
"decmpfs chunked fork chunk out of bounds",
))?;
let chunk_uncompressed = uncompressed_size
.checked_sub(out.len())
.ok_or(ApfsError::Decmpfs("decmpfs chunked fork size underflow"))? .min(CHUNK_SIZE);
let decoded = match algorithm {
Algorithm::Lzvn => lzvn_decode(chunk, chunk_uncompressed)?,
Algorithm::Lzfse => lzfse_decode(chunk)?,
Algorithm::Uncompressed => chunk.to_vec(),
_ => return unreachable_algorithm(), };
out.extend_from_slice(&decoded);
src = end;
}
Ok(out)
}
const MAX_DECMPFS_CAP: usize = 1 << 30;
fn inflate(data: &[u8]) -> crate::Result<Vec<u8>> {
let mut decoder = flate2::read::ZlibDecoder::new(data);
let mut out = Vec::new();
decoder
.read_to_end(&mut out)
.map_err(|_| ApfsError::Decmpfs("decmpfs zlib codec error"))?;
Ok(out)
}
fn lzvn_decode(chunk: &[u8], uncompressed_len: usize) -> crate::Result<Vec<u8>> {
lzvn::decode(chunk, uncompressed_len)
.map_err(|_| ApfsError::Decmpfs("decmpfs lzvn codec error"))
}
fn lzfse_decode(stream: &[u8]) -> crate::Result<Vec<u8>> {
let mut out = Vec::new();
lzfse_rust::decode_bytes(stream, &mut out)
.map_err(|_| ApfsError::Decmpfs("decmpfs lzfse codec error"))?;
Ok(out)
}
fn le_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
let end = offset
.checked_add(4)
.ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
let bytes = data
.get(offset..end)
.ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
fn be_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
let end = offset
.checked_add(4)
.ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
let bytes = data
.get(offset..end)
.ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
fn le_u64(data: &[u8], offset: usize) -> crate::Result<u64> {
let end = offset
.checked_add(8)
.ok_or(ApfsError::Decmpfs("decmpfs read offset overflow"))?;
let bytes = data
.get(offset..end)
.ok_or(ApfsError::Decmpfs("decmpfs read out of bounds"))?;
let mut a = [0u8; 8];
a.copy_from_slice(bytes);
Ok(u64::from_le_bytes(a))
}