use alloc::{vec, vec::Vec};
use crate::{
error::{Error, Result},
phys::CompressionType,
};
pub(crate) const MAX_BLOCK_LSIZE: usize = 16 * 1024 * 1024;
pub(crate) fn decompress(comp: CompressionType, src: &[u8], lsize: usize) -> Result<Vec<u8>> {
if lsize > MAX_BLOCK_LSIZE {
return Err(Error::FileTooLarge {
size: lsize as u64,
max: MAX_BLOCK_LSIZE as u64,
});
}
match comp {
CompressionType::Off => {
let region = src.get(..lsize).ok_or(Error::BadCompression {
comp: 2,
token: "off_short",
})?;
Ok(region.to_vec())
}
CompressionType::Zle => zle_decode(src, lsize),
CompressionType::Lzjb => lzjb_decode(src, lsize),
CompressionType::Lz4 => lz4_decode(src, lsize),
CompressionType::Gzip1
| CompressionType::Gzip2
| CompressionType::Gzip3
| CompressionType::Gzip4
| CompressionType::Gzip5
| CompressionType::Gzip6
| CompressionType::Gzip7
| CompressionType::Gzip8
| CompressionType::Gzip9 => gzip_decode(src, lsize),
CompressionType::Zstd => zstd_decode(src, lsize),
CompressionType::Inherit | CompressionType::On | CompressionType::Empty => {
Err(Error::UnsupportedCompression { comp: comp as u8 })
}
}
}
fn lzjb_decode(src: &[u8], lsize: usize) -> Result<Vec<u8>> {
use crate::compression::{Decompression, LzjbDecoder};
let mut dst = vec![0u8; lsize];
LzjbDecoder {}
.decompress(&mut dst, src, 0)
.map_err(|_| Error::BadCompression {
comp: 3,
token: "lzjb_bad",
})?;
Ok(dst)
}
fn zle_decode(src: &[u8], lsize: usize) -> Result<Vec<u8>> {
let mut dst = vec![0u8; lsize];
let mut s = 0usize;
let mut d = 0usize;
let bad = || Error::BadCompression {
comp: 14,
token: "zle_bad",
};
while d < lsize {
let c = *src.get(s).ok_or_else(bad)?;
s += 1;
if c < 0x40 {
let n = usize::from(c) + 1;
let run = src.get(s..s + n).ok_or_else(bad)?;
let out = dst.get_mut(d..d + n).ok_or_else(bad)?;
out.copy_from_slice(run);
s += n;
d += n;
} else {
let n = usize::from(c) - 0x40 + 1;
if d + n > lsize {
return Err(bad());
}
d += n;
}
}
Ok(dst)
}
#[cfg(feature = "lz4")]
fn lz4_decode(src: &[u8], lsize: usize) -> Result<Vec<u8>> {
let bad = |token| Error::BadCompression { comp: 15, token };
let len_bytes = src.get(..4).ok_or_else(|| bad("lz4_no_prefix"))?;
let clen = u32::from_be_bytes(len_bytes.try_into().unwrap()) as usize;
let block = src.get(4..4 + clen).ok_or_else(|| bad("lz4_short"))?;
let mut dst = vec![0u8; lsize];
let n = lz4_flex::block::decompress_into(block, &mut dst).map_err(|_| bad("lz4_decode"))?;
if n != lsize {
return Err(bad("lz4_len"));
}
Ok(dst)
}
#[cfg(not(feature = "lz4"))]
fn lz4_decode(_src: &[u8], _lsize: usize) -> Result<Vec<u8>> {
Err(Error::UnsupportedCompression { comp: 15 })
}
#[cfg(feature = "gzip")]
fn gzip_decode(src: &[u8], lsize: usize) -> Result<Vec<u8>> {
let bad = |token| Error::BadCompression { comp: 5, token };
let mut dst = vec![0u8; lsize];
let n = miniz_oxide::inflate::decompress_slice_iter_to_slice(
&mut dst,
core::iter::once(src),
true, true, )
.map_err(|_| bad("gzip_decode"))?;
if n != lsize {
return Err(bad("gzip_len"));
}
Ok(dst)
}
#[cfg(not(feature = "gzip"))]
fn gzip_decode(_src: &[u8], _lsize: usize) -> Result<Vec<u8>> {
Err(Error::UnsupportedCompression { comp: 5 })
}
#[cfg(feature = "zstd")]
fn zstd_decode(src: &[u8], lsize: usize) -> Result<Vec<u8>> {
use ruzstd::io::Read as _;
const ZSTD_MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
let bad = |token| Error::BadCompression { comp: 16, token };
let hdr = src.get(..8).ok_or_else(|| bad("zstd_no_hdr"))?;
let clen = u32::from_be_bytes(hdr[..4].try_into().unwrap()) as usize;
let frame = src.get(8..8 + clen).ok_or_else(|| bad("zstd_short"))?;
let mut framed = Vec::with_capacity(4 + frame.len());
framed.extend_from_slice(&ZSTD_MAGIC);
framed.extend_from_slice(frame);
let mut dec = ruzstd::decoding::StreamingDecoder::new(framed.as_slice())
.map_err(|_| bad("zstd_frame"))?;
let mut dst = vec![0u8; lsize];
dec.read_exact(&mut dst).map_err(|_| bad("zstd_decode"))?;
let mut extra = [0u8; 1];
if dec.read(&mut extra).is_ok_and(|n| n != 0) {
return Err(bad("zstd_len"));
}
Ok(dst)
}
#[cfg(not(feature = "zstd"))]
fn zstd_decode(_src: &[u8], _lsize: usize) -> Result<Vec<u8>> {
Err(Error::UnsupportedCompression { comp: 16 })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn off_copies_logical_prefix() {
let src = b"hello world padding-past-lsize";
let out = decompress(CompressionType::Off, src, 11).unwrap();
assert_eq!(&out, b"hello world");
}
#[test]
fn zle_literals_and_zero_runs() {
let src = [0x02, b'a', b'b', b'c', 0x43, 0x00, b'z'];
let out = zle_decode(&src, 8).unwrap();
assert_eq!(out, [b'a', b'b', b'c', 0, 0, 0, 0, b'z']);
}
#[test]
fn oversize_lsize_refused() {
let e = decompress(CompressionType::Off, b"x", MAX_BLOCK_LSIZE + 1).unwrap_err();
assert!(matches!(e, Error::FileTooLarge { .. }));
}
#[test]
fn off_short_source_errors() {
let e = decompress(CompressionType::Off, b"hi", 8).unwrap_err();
assert!(matches!(e, Error::BadCompression { .. }));
}
}