use crate::Result;
pub fn decode_adc(src: &[u8], out: &mut [u8]) -> Result<usize> {
let mut sp = 0usize;
let mut dp = 0usize;
while sp < src.len() {
let op = src[sp];
sp += 1;
if op < 0x80 {
let len = op as usize + 1;
if sp + len > src.len() {
return Err(crate::Error::InvalidImage(format!(
"dmg/adc: literal run of {len} bytes runs past source ({} of {} consumed)",
sp,
src.len()
)));
}
if dp + len > out.len() {
return Err(crate::Error::InvalidImage(format!(
"dmg/adc: literal run of {len} bytes overflows output ({}+{}>{})",
dp,
len,
out.len()
)));
}
out[dp..dp + len].copy_from_slice(&src[sp..sp + len]);
sp += len;
dp += len;
} else if op < 0xC0 {
if sp >= src.len() {
return Err(crate::Error::InvalidImage(
"dmg/adc: short reference truncated".into(),
));
}
let b = src[sp];
sp += 1;
let len = (((op >> 2) & 0x0F) as usize) + 3;
let dist = ((((op & 0x03) as usize) << 8) | b as usize) + 1;
adc_copy(out, &mut dp, dist, len)?;
} else {
if sp + 2 > src.len() {
return Err(crate::Error::InvalidImage(
"dmg/adc: long reference truncated".into(),
));
}
let hi = src[sp] as usize;
let lo = src[sp + 1] as usize;
sp += 2;
let len = ((op & 0x3F) as usize) + 4;
let dist = ((hi << 8) | lo) + 1;
adc_copy(out, &mut dp, dist, len)?;
}
}
Ok(dp)
}
fn adc_copy(out: &mut [u8], dp: &mut usize, dist: usize, len: usize) -> Result<()> {
if dist == 0 || dist > *dp {
return Err(crate::Error::InvalidImage(format!(
"dmg/adc: back-reference distance {dist} out of range (dp = {dp})",
dp = *dp
)));
}
if *dp + len > out.len() {
return Err(crate::Error::InvalidImage(format!(
"dmg/adc: back-reference of {len} bytes overflows output ({}+{}>{})",
*dp,
len,
out.len()
)));
}
for i in 0..len {
out[*dp + i] = out[*dp + i - dist];
}
*dp += len;
Ok(())
}
#[cfg(any(feature = "dmg-bzip2", feature = "dmg-lzfse"))]
fn decode_chunk<A: compcol::Algorithm>(
src: &[u8],
plain_len: usize,
label: &str,
) -> Result<Vec<u8>> {
use compcol::{Decoder, Status};
let mut dec = A::decoder();
let mut out = Vec::with_capacity(plain_len);
let mut scratch = vec![0u8; 64 * 1024];
let mut consumed = 0usize;
let err = |e| crate::Error::InvalidImage(format!("dmg: {label} chunk decode failed: {e}"));
let overflow = || {
crate::Error::InvalidImage(format!(
"dmg: {label} chunk expanded past {plain_len} bytes"
))
};
loop {
let (p, status) = dec.decode(&src[consumed..], &mut scratch).map_err(err)?;
out.extend_from_slice(&scratch[..p.written]);
consumed += p.consumed;
if out.len() > plain_len {
return Err(overflow());
}
match status {
Status::StreamEnd => return Ok(out),
Status::OutputFull => continue,
Status::InputEmpty => break,
}
}
loop {
let (p, status) = dec.finish(&mut scratch).map_err(err)?;
out.extend_from_slice(&scratch[..p.written]);
if out.len() > plain_len {
return Err(overflow());
}
if matches!(status, Status::StreamEnd) || p.written == 0 {
break;
}
}
Ok(out)
}
#[cfg(feature = "dmg-bzip2")]
pub fn decode_bzip2(src: &[u8], plain_len: usize) -> Result<Vec<u8>> {
let out = decode_chunk::<compcol::bzip2::Bzip2>(src, plain_len, "bzip2")?;
if out.len() != plain_len {
return Err(crate::Error::InvalidImage(format!(
"dmg: bzip2 chunk inflated to {} bytes but sector_count*512 = {}",
out.len(),
plain_len
)));
}
Ok(out)
}
#[cfg(not(feature = "dmg-bzip2"))]
pub fn decode_bzip2(_src: &[u8], _plain_len: usize) -> Result<Vec<u8>> {
Err(crate::Error::Unsupported(
"dmg: bzip2 chunks require the `dmg-bzip2` Cargo feature".into(),
))
}
#[cfg(feature = "dmg-lzfse")]
pub fn decode_lzfse(src: &[u8], plain_len: usize) -> Result<Vec<u8>> {
let out = decode_chunk::<compcol::lzfse::Lzfse>(src, plain_len, "lzfse")?;
if out.len() != plain_len {
return Err(crate::Error::InvalidImage(format!(
"dmg: lzfse chunk inflated to {} bytes but sector_count*512 = {}",
out.len(),
plain_len
)));
}
Ok(out)
}
#[cfg(not(feature = "dmg-lzfse"))]
pub fn decode_lzfse(_src: &[u8], _plain_len: usize) -> Result<Vec<u8>> {
Err(crate::Error::Unsupported(
"dmg: LZFSE chunks require the `dmg-lzfse` Cargo feature".into(),
))
}
#[cfg(feature = "lzma")]
pub fn decode_lzma(src: &[u8], plain_len: usize) -> Result<Vec<u8>> {
let out = crate::compression::decompress(crate::compression::Algo::Lzma, src, plain_len)?;
if out.len() != plain_len {
return Err(crate::Error::InvalidImage(format!(
"dmg: lzma chunk inflated to {} bytes but sector_count*512 = {}",
out.len(),
plain_len
)));
}
Ok(out)
}
#[cfg(not(feature = "lzma"))]
pub fn decode_lzma(_src: &[u8], _plain_len: usize) -> Result<Vec<u8>> {
Err(crate::Error::Unsupported(
"dmg: LZMA chunks require the `lzma` Cargo feature".into(),
))
}
pub fn decode_adc_chunk(src: &[u8], plain_len: usize) -> Result<Vec<u8>> {
let mut out = vec![0u8; plain_len];
let n = decode_adc(src, &mut out)?;
if n != plain_len {
return Err(crate::Error::InvalidImage(format!(
"dmg: ADC chunk produced {n} bytes but sector_count*512 = {plain_len}"
)));
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn adc_literal(data: &[u8]) -> Vec<u8> {
assert!(!data.is_empty() && data.len() <= 128);
let mut v = Vec::with_capacity(1 + data.len());
v.push((data.len() - 1) as u8);
v.extend_from_slice(data);
v
}
#[test]
fn adc_literal_only() {
let payload = b"hello world";
let stream = adc_literal(payload);
let mut out = vec![0u8; payload.len()];
let n = decode_adc(&stream, &mut out).unwrap();
assert_eq!(n, payload.len());
assert_eq!(&out[..], payload);
}
#[test]
fn adc_long_literal_run() {
let payload: Vec<u8> = (0..128u8).collect();
let stream = adc_literal(&payload);
let mut out = vec![0u8; 128];
let n = decode_adc(&stream, &mut out).unwrap();
assert_eq!(n, 128);
assert_eq!(out, payload);
}
#[test]
fn adc_short_reference() {
let mut stream = adc_literal(b"ABCD");
stream.push(0x84);
stream.push(0x03);
let mut out = vec![0u8; 8];
let n = decode_adc(&stream, &mut out).unwrap();
assert_eq!(n, 8);
assert_eq!(&out[..], b"ABCDABCD");
}
#[test]
fn adc_short_reference_overlap_run() {
let mut stream = adc_literal(b"a");
stream.push(0x80);
stream.push(0x00);
let mut out = vec![0u8; 4];
let n = decode_adc(&stream, &mut out).unwrap();
assert_eq!(n, 4);
assert_eq!(&out[..], b"aaaa");
}
#[test]
fn adc_long_reference() {
let mut stream = Vec::new();
stream.push(99u8); stream.extend(std::iter::repeat_n(0xAA, 100));
stream.push(99u8);
stream.extend(std::iter::repeat_n(0xBB, 100));
stream.push(0xC0 | 63); stream.push(0x00);
stream.push(0xC7);
let mut out = vec![0u8; 267];
let n = decode_adc(&stream, &mut out).unwrap();
assert_eq!(n, 267);
assert!(out[0..100].iter().all(|&b| b == 0xAA));
assert!(out[100..200].iter().all(|&b| b == 0xBB));
assert!(out[200..267].iter().all(|&b| b == 0xAA));
}
#[test]
fn adc_rejects_distance_zero() {
let stream = vec![0x80, 0x00];
let mut out = vec![0u8; 4];
let err = decode_adc(&stream, &mut out).unwrap_err();
match err {
crate::Error::InvalidImage(_) => {}
_ => panic!("expected InvalidImage, got {err:?}"),
}
}
#[test]
fn adc_rejects_truncated_short_reference() {
let stream = vec![0x80];
let mut out = vec![0u8; 4];
assert!(decode_adc(&stream, &mut out).is_err());
}
#[test]
fn adc_rejects_truncated_long_reference() {
let stream = vec![0xC0, 0x00];
let mut out = vec![0u8; 4];
assert!(decode_adc(&stream, &mut out).is_err());
}
#[test]
fn adc_rejects_literal_overrun() {
let stream = vec![0x04, 0x01, 0x02];
let mut out = vec![0u8; 8];
assert!(decode_adc(&stream, &mut out).is_err());
}
#[cfg(feature = "dmg-bzip2")]
#[test]
fn bzip2_roundtrip_via_libbz2_independent_vector() {
let compressed: &[u8] = &[
0x42, 0x5A, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0x4E, 0xEC, 0xE8, 0x36,
0x00, 0x00, 0x02, 0x51, 0x80, 0x00, 0x10, 0x40, 0x00, 0x06, 0x44, 0x90, 0x80, 0x20,
0x00, 0x31, 0x06, 0x4C, 0x41, 0x01, 0xA7, 0xA9, 0xA5, 0x80, 0xBB, 0x94, 0x31, 0xF8,
0xBB, 0x92, 0x29, 0xC2, 0x84, 0x82, 0x77, 0x67, 0x41, 0xB0,
];
let plain = decode_bzip2(compressed, b"hello world\n".len()).unwrap();
assert_eq!(plain, b"hello world\n");
}
#[cfg(feature = "lzma")]
#[test]
fn lzma_roundtrip() {
let plain = b"the quick brown fox jumps over the lazy dog".repeat(8);
let compressed =
crate::compression::compress(crate::compression::Algo::Lzma, &plain).unwrap();
let out = decode_lzma(&compressed, plain.len()).unwrap();
assert_eq!(out, plain);
}
}