use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
use crate::shared::varint;
pub(crate) const LZ: Desc = Desc {
id: 19,
min_version: 24,
regens: &[Type::Serial],
variable_regens: false,
inputs: &[Type::Serial, Type::Numeric, Type::Numeric, Type::Numeric],
variable_inputs: 0,
decode,
};
fn decode(
ctx: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let [literals, offsets, literal_lens, match_lens] = inputs else {
return Err(corrupt());
};
let sequences = offsets.len();
if literal_lens.len() != sequences || match_lens.len() != sequences {
return Err(corrupt());
}
let (size, used) = varint::decode(ctx.header).map_err(|_| corrupt())?;
if used != ctx.header.len() {
return Err(corrupt());
}
let size = usize::try_from(size).unwrap_or(usize::MAX);
let out = regens[0].fixed(1, size)?;
let sequences = Sequences {
literals: literals.bytes,
offsets: Numbers::new(offsets),
literal_lens: Numbers::new(literal_lens),
match_lens: Numbers::new(match_lens),
count: sequences,
};
sequences.expand(out, size)
}
#[derive(Copy, Clone)]
struct Numbers<'a> {
bytes: &'a [u8],
width: usize,
}
impl<'a> Numbers<'a> {
fn new(input: &Input<'a>) -> Self {
Self {
bytes: input.bytes,
width: input.width,
}
}
fn get(&self, index: usize) -> u64 {
let mut value = [0u8; 8];
let start = index * self.width;
let width = self.width.min(8);
value[..width].copy_from_slice(&self.bytes[start..start + width]);
u64::from_le_bytes(value)
}
}
struct Sequences<'a> {
literals: &'a [u8],
offsets: Numbers<'a>,
literal_lens: Numbers<'a>,
match_lens: Numbers<'a>,
count: usize,
}
impl Sequences<'_> {
fn expand(&self, out: &mut alloc::vec::Vec<u8>, size: usize) -> Result<(), DecodeError> {
let mut literals = self.literals;
for sequence in 0..self.count {
let literal_len =
usize::try_from(self.literal_lens.get(sequence)).unwrap_or(usize::MAX);
let offset = usize::try_from(self.offsets.get(sequence)).unwrap_or(usize::MAX);
let match_len = usize::try_from(self.match_lens.get(sequence)).unwrap_or(usize::MAX);
if literal_len > literals.len() || literal_len > size - out.len() {
return Err(corrupt());
}
let (now, rest) = literals.split_at(literal_len);
out.extend_from_slice(now);
literals = rest;
if offset == 0 || offset > out.len() || match_len > size - out.len() {
return Err(corrupt());
}
copy_match(out, offset, match_len);
}
if literals.len() > size - out.len() {
return Err(corrupt());
}
out.extend_from_slice(literals);
if out.len() == size {
Ok(())
} else {
Err(corrupt())
}
}
}
fn copy_match(out: &mut alloc::vec::Vec<u8>, offset: usize, length: usize) {
let from = out.len() - offset;
let mut done = 0;
while done < length {
let chunk = offset.min(length - done);
out.extend_from_within(from..from + chunk);
done += chunk;
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use super::super::State;
use super::*;
use crate::Backend;
type Case<'a> = (&'a [u8], Streams<'a>, &'a [u8]);
type Streams<'a> = [(usize, &'a [u64]); 3];
fn numeric(width: usize, values: &[u64]) -> (usize, Vec<u8>) {
let bytes = values
.iter()
.flat_map(|value| value.to_le_bytes()[..width].to_vec())
.collect();
(width, bytes)
}
fn run(
header: &[u8],
literals: &[u8],
streams: Streams<'_>,
budget: usize,
) -> Result<Vec<u8>, DecodeError> {
let encoded: Vec<_> = streams
.iter()
.map(|&(width, values)| numeric(width, values))
.collect();
let mut inputs = vec![Input::serial(literals)];
inputs.extend(encoded.iter().map(|(width, bytes)| Input {
ty: Type::Numeric,
width: *width,
bytes,
lens: &[],
}));
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, header);
let mut regens = [Stream::default()];
regens[0].prepare(Type::Serial, budget);
decode(&mut ctx, &inputs, &mut regens)?;
regens[0].validate()?;
Ok(regens[0].bytes().to_vec())
}
#[test]
fn sequences_expand_with_literals_matches_and_a_tail() {
let out = run(
&[16],
b"abcdTAIL",
[(2, &[2, 7]), (1, &[2, 2]), (8, &[5, 3])],
64,
)
.unwrap();
assert_eq!(out, b"abababacdabaTAIL");
let wide = run(
&[16],
b"abcdTAIL",
[(8, &[2, 7]), (4, &[2, 2]), (2, &[5, 3])],
64,
)
.unwrap();
assert_eq!(wide, out);
assert_eq!(
run(&[4], b"abcd", [(1, &[]), (1, &[]), (1, &[])], 64).unwrap(),
b"abcd"
);
assert_eq!(
run(&[0], b"", [(1, &[]), (1, &[]), (1, &[])], 64).unwrap(),
b""
);
}
#[test]
fn long_matches_repeat_short_offsets() {
let out = run(&[103], b"xy", [(1, &[2]), (1, &[2]), (2, &[101])], 128).unwrap();
let mut expected = b"xy".to_vec();
expected.extend(b"xy".iter().cycle().take(101));
assert_eq!(out, expected);
}
#[test]
fn mismatched_counts_and_headers_are_rejected() {
let ok = [(1usize, &[1u64][..]), (1, &[1]), (1, &[1])];
assert!(run(&[3], b"ab", ok, 64).is_ok());
assert_eq!(
run(&[3], b"ab", [(1, &[1]), (1, &[1, 1]), (1, &[1])], 64),
Err(corrupt())
);
assert_eq!(
run(&[3], b"ab", [(1, &[1]), (1, &[1]), (1, &[])], 64),
Err(corrupt())
);
assert_eq!(run(&[3, 0], b"ab", ok, 64), Err(corrupt()));
assert_eq!(run(&[], b"ab", ok, 64), Err(corrupt()));
assert_eq!(run(&[0x80], b"ab", ok, 64), Err(corrupt()));
assert_eq!(run(&[100], b"ab", ok, 64), Err(corrupt()));
assert_eq!(run(&[4], b"ab", ok, 64), Err(corrupt()));
assert_eq!(run(&[2], b"ab", ok, 64), Err(corrupt()));
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, &[0]);
let mut regens = [Stream::default()];
regens[0].prepare(Type::Serial, 8);
assert_eq!(decode(&mut ctx, &[], &mut regens), Err(corrupt()));
}
#[test]
fn corrupt_sequences_are_rejected() {
let cases: [Case<'_>; 7] = [
(b"ab", [(1, &[1]), (1, &[3]), (1, &[1])], &[7]),
(b"abcd", [(1, &[1]), (1, &[3]), (1, &[1])], &[2]),
(b"ab", [(1, &[0]), (1, &[2]), (1, &[2])], &[6]),
(b"ab", [(1, &[3]), (1, &[2]), (1, &[2])], &[6]),
(b"ab", [(1, &[1]), (1, &[2]), (1, &[9])], &[6]),
(b"abcdef", [(1, &[1]), (1, &[2]), (1, &[1])], &[5]),
(b"ab", [(1, &[1]), (1, &[2]), (1, &[1])], &[9]),
];
for (literals, streams, header) in cases {
assert_eq!(
run(header, literals, streams, 64),
Err(corrupt()),
"{literals:?}"
);
}
for streams in [
[(8, &[1][..]), (8, &[u64::MAX]), (8, &[1])],
[(8, &[u64::MAX]), (8, &[2]), (8, &[1])],
[(8, &[1]), (8, &[2]), (8, &[u64::MAX])],
[(8, &[1]), (8, &[1 << 63]), (8, &[1])],
] {
assert_eq!(run(&[8], b"ab", streams, 64), Err(corrupt()));
}
}
}