use crate::compress::{Decompressor, Error, LZO2A_MAX_RATIO, Result, check_claim, invalid};
const CODEC: &str = "lzo2a";
#[derive(Debug, Clone, Copy)]
pub struct Lzo2a;
struct Stream<'a> {
input: &'a [u8],
at: usize,
bits: u32,
held: u32,
}
impl Stream<'_> {
fn byte(&mut self) -> Result<u8> {
let b = *self.input.get(self.at).ok_or_else(|| {
invalid(
CODEC,
self.at,
"stream ends before its end code: expected another byte",
)
})?;
self.at = self.at.saturating_add(1);
Ok(b)
}
fn flags(&mut self, n: u32) -> Result<u32> {
if self.held < n {
let b = self.byte()?;
self.bits |= u32::from(b) << self.held;
self.held = self.held.saturating_add(8);
}
let v = self.bits & !(u32::MAX << n);
self.bits >>= n;
self.held = self.held.saturating_sub(n);
Ok(v)
}
}
impl Decompressor for Lzo2a {
fn decompress(&self, input: &[u8], expected: usize) -> Result<Vec<u8>> {
check_claim(CODEC, input.len(), expected, LZO2A_MAX_RATIO)?;
let mut out: Vec<u8> = Vec::new();
out.try_reserve_exact(expected).map_err(|_| Error::Limit)?;
let mut s = Stream {
input,
at: 0,
bits: 0,
held: 0,
};
loop {
let operands;
let (length, distance) = if s.flags(1)? == 0 {
let b = s.byte()?;
if out.len() == expected {
return Err(Error::Size {
expected,
actual: expected.saturating_add(1),
});
}
out.push(b);
continue;
} else if s.flags(1)? == 0 {
let length = 2 + s.flags(2)? as usize;
operands = s.at;
(length, 1 + usize::from(s.byte()?))
} else {
operands = s.at;
let a = s.byte()?;
let b = s.byte()?;
let distance = usize::from(a & 31) | usize::from(b) << 5;
let short = usize::from(a >> 5);
if short == 0 {
let mut length = 9usize;
loop {
match s.byte()? {
0 => length = length.checked_add(255).ok_or(Error::Limit)?,
n => {
length = length.checked_add(usize::from(n)).ok_or(Error::Limit)?;
break;
}
}
}
(length, distance)
} else if distance == 0 {
break;
} else {
(short + 2, distance)
}
};
let from = out
.len()
.checked_sub(distance)
.filter(|_| distance > 0)
.ok_or_else(|| {
invalid(
CODEC,
operands,
format!(
"match {distance} bytes back at output position {}: expected at most {}",
out.len(),
out.len()
),
)
})?;
let end = out.len().checked_add(length).ok_or(Error::Limit)?;
if end > expected {
return Err(Error::Size {
expected,
actual: end,
});
}
for k in from..from.saturating_add(length) {
let b = *out
.get(k)
.ok_or_else(|| invalid(CODEC, operands, "match reads past the output"))?;
out.push(b);
}
}
if s.at != input.len() {
return Err(invalid(
CODEC,
s.at,
"input after the end code: expected the stream to end here",
));
}
if out.len() != expected {
return Err(Error::Size {
expected,
actual: out.len(),
});
}
Ok(out)
}
}