use super::DecompressError;
use super::crc32::crc32;
use super::inflate::{self, RunningChecksum};
use alloc::vec::Vec;
const ID1: u8 = 0x1f;
const ID2: u8 = 0x8b;
const CM_DEFLATE: u8 = 8;
const FHCRC: u8 = 1 << 1;
const FEXTRA: u8 = 1 << 2;
const FNAME: u8 = 1 << 3;
const FCOMMENT: u8 = 1 << 4;
const RESERVED: u8 = 0xe0;
fn member_deflate(data: &[u8]) -> Result<(&[u8], usize), DecompressError> {
let mut i = 0usize;
let id1 = *data.get(i).ok_or(DecompressError::InvalidInput)?;
i = i.saturating_add(1);
let id2 = *data.get(i).ok_or(DecompressError::InvalidInput)?;
i = i.saturating_add(1);
if id1 != ID1 || id2 != ID2 {
return Err(DecompressError::InvalidInput);
}
let cm = *data.get(i).ok_or(DecompressError::InvalidInput)?;
i = i.saturating_add(1);
if cm != CM_DEFLATE {
return Err(DecompressError::InvalidInput);
}
let flg = *data.get(i).ok_or(DecompressError::InvalidInput)?;
i = i.saturating_add(1);
if flg & RESERVED != 0 {
return Err(DecompressError::InvalidInput);
}
i = i.checked_add(6).ok_or(DecompressError::InvalidInput)?;
if i > data.len() {
return Err(DecompressError::InvalidInput);
}
if flg & FEXTRA != 0 {
let b0 = *data.get(i).ok_or(DecompressError::InvalidInput)?;
let b1 = *data
.get(i.saturating_add(1))
.ok_or(DecompressError::InvalidInput)?;
let xlen = usize::from(u16::from_le_bytes([b0, b1]));
i = i
.checked_add(2)
.and_then(|v| v.checked_add(xlen))
.ok_or(DecompressError::InvalidInput)?;
if i > data.len() {
return Err(DecompressError::InvalidInput);
}
}
if flg & FNAME != 0 {
i = skip_cstr(data, i)?;
}
if flg & FCOMMENT != 0 {
i = skip_cstr(data, i)?;
}
if flg & FHCRC != 0 {
let header = data.get(..i).ok_or(DecompressError::InvalidInput)?;
let expect = u16::try_from(crc32(header) & 0xffff).map_err(|_| DecompressError::InvalidInput)?;
let b0 = *data.get(i).ok_or(DecompressError::InvalidInput)?;
let b1 = *data
.get(i.saturating_add(1))
.ok_or(DecompressError::InvalidInput)?;
let got = u16::from_le_bytes([b0, b1]);
if got != expect {
return Err(DecompressError::InvalidInput);
}
i = i.checked_add(2).ok_or(DecompressError::InvalidInput)?;
}
let deflate = data.get(i..).ok_or(DecompressError::InvalidInput)?;
Ok((deflate, i))
}
fn check_trailer(
data: &[u8],
trailer_off: usize,
out: &[u8],
crc: RunningChecksum,
) -> Result<(), DecompressError> {
let trailer = data
.get(trailer_off..trailer_off.saturating_add(8))
.ok_or(DecompressError::InvalidInput)?;
let crc_got = u32::from_le_bytes([
*trailer.first().ok_or(DecompressError::InvalidInput)?,
*trailer.get(1).ok_or(DecompressError::InvalidInput)?,
*trailer.get(2).ok_or(DecompressError::InvalidInput)?,
*trailer.get(3).ok_or(DecompressError::InvalidInput)?,
]);
let isize = u32::from_le_bytes([
*trailer.get(4).ok_or(DecompressError::InvalidInput)?,
*trailer.get(5).ok_or(DecompressError::InvalidInput)?,
*trailer.get(6).ok_or(DecompressError::InvalidInput)?,
*trailer.get(7).ok_or(DecompressError::InvalidInput)?,
]);
if crc_got != crc.crc_value() {
return Err(DecompressError::InvalidInput);
}
#[allow(clippy::cast_possible_truncation)] let isize_expect = out.len() as u32;
if isize != isize_expect {
return Err(DecompressError::InvalidInput);
}
Ok(())
}
pub(super) fn decompress_member_owned(
data: &[u8],
max_out: usize,
) -> Result<Vec<u8>, DecompressError> {
let (deflate, i) = member_deflate(data)?;
let mut none = RunningChecksum::None;
let (out, consumed) = inflate::inflate_owned(deflate, max_out, &mut none)?;
let trailer_off = i
.checked_add(consumed)
.ok_or(DecompressError::InvalidInput)?;
let crc = RunningChecksum::Crc(crc32(&out));
check_trailer(data, trailer_off, &out, crc)?;
Ok(out)
}
pub(super) fn decompress_member(
data: &[u8],
max_out: usize,
out: &mut Vec<u8>,
) -> Result<(), DecompressError> {
if !out.is_empty() {
out.clear();
}
let (deflate, i) = member_deflate(data)?;
let mut crc = RunningChecksum::crc();
let consumed = inflate::inflate(deflate, max_out, &mut crc, out)?;
let trailer_off = i
.checked_add(consumed)
.ok_or(DecompressError::InvalidInput)?;
check_trailer(data, trailer_off, out, crc)
}
fn skip_cstr(
data: &[u8],
mut i: usize,
) -> Result<usize, DecompressError> {
loop {
let b = *data.get(i).ok_or(DecompressError::InvalidInput)?;
i = i.checked_add(1).ok_or(DecompressError::InvalidInput)?;
if b == 0 {
return Ok(i);
}
}
}