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(feature = "dmg-bzip2")]
pub fn decode_bzip2(src: &[u8], plain_len: usize) -> Result<Vec<u8>> {
use std::io::Read;
let mut rdr = bzip2_rs::DecoderReader::new(src);
let mut out = Vec::with_capacity(plain_len);
rdr.read_to_end(&mut out)
.map_err(|e| crate::Error::InvalidImage(format!("dmg: bzip2 chunk decode failed: {e}")))?;
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 mut out = Vec::with_capacity(plain_len);
lzfse_rust::decode_bytes(src, &mut out)
.map_err(|e| crate::Error::InvalidImage(format!("dmg: lzfse chunk decode failed: {e}")))?;
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 mut input = std::io::BufReader::new(src);
let mut out = Vec::with_capacity(plain_len);
lzma_rs::lzma_decompress(&mut input, &mut out)
.map_err(|e| crate::Error::InvalidImage(format!("dmg: lzma chunk decode failed: {e}")))?;
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_via_lzma_rs() {
let plain = b"the quick brown fox jumps over the lazy dog".repeat(8);
let mut compressed = Vec::new();
{
let mut input = std::io::Cursor::new(&plain);
lzma_rs::lzma_compress(&mut input, &mut compressed).unwrap();
}
let out = decode_lzma(&compressed, plain.len()).unwrap();
assert_eq!(out, plain);
}
}