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 CONSTANT_SERIAL: Desc = Desc {
id: 44,
min_version: 11,
regens: &[Type::Serial],
variable_regens: false,
inputs: &[Type::Serial],
variable_inputs: 0,
decode,
};
pub(crate) const CONSTANT_FIXED: Desc = Desc {
id: 45,
min_version: 11,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[Type::Struct],
variable_inputs: 0,
decode,
};
fn decode(
ctx: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let input = &inputs[0];
if input.len() != 1 {
return Err(corrupt());
}
let (count, used) = varint::decode(ctx.header).map_err(|_| corrupt())?;
if used != ctx.header.len() || count == 0 {
return Err(corrupt());
}
let count = usize::try_from(count).unwrap_or(usize::MAX);
let out = regens[0].fixed(input.width, count)?;
if input.width == 1 {
out.resize(count, input.bytes[0]);
} else {
for _ in 0..count {
out.extend_from_slice(input.bytes);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
const DESC: Desc = CONSTANT_SERIAL;
use super::super::State;
use super::*;
use crate::Backend;
fn run(desc: &Desc, header: &[u8], input: Input<'_>) -> Result<Stream, DecodeError> {
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, header);
let mut regens = [Stream::default()];
regens[0].prepare(desc.regen_type(0), 1 << 16);
(desc.decode)(&mut ctx, &[input], &mut regens)?;
let [stream] = regens;
stream.validate()?;
Ok(stream)
}
fn records(width: usize, bytes: &[u8]) -> Input<'_> {
Input {
ty: Type::Struct,
width,
bytes,
lens: &[],
}
}
#[test]
fn a_byte_repeats_for_serial_streams() {
let out = run(&DESC, &[5], Input::serial(&[0xab])).unwrap();
assert_eq!(out.bytes(), [0xab; 5]);
assert_eq!((out.ty(), out.as_input().width), (Type::Serial, 1));
let out = run(&DESC, &[0xc8, 0x01], Input::serial(&[7])).unwrap();
assert_eq!(out.bytes(), [7; 200]);
}
#[test]
fn a_record_repeats_for_struct_streams() {
let out = run(&CONSTANT_FIXED, &[3], records(2, b"ab")).unwrap();
assert_eq!(out.bytes(), b"ababab");
assert_eq!((out.ty(), out.as_input().width), (Type::Struct, 2));
let out = run(&CONSTANT_FIXED, &[1], records(40, &[9; 40])).unwrap();
assert_eq!(out.bytes(), [9; 40]);
let out = run(&CONSTANT_FIXED, &[2], records(1, b"x")).unwrap();
assert_eq!(out.bytes(), b"xx");
}
#[test]
fn the_input_must_hold_exactly_one_element() {
assert_eq!(run(&DESC, &[2], Input::serial(&[])).err(), Some(corrupt()));
assert_eq!(
run(&DESC, &[2], Input::serial(&[1, 2])).err(),
Some(corrupt())
);
assert_eq!(
run(&CONSTANT_FIXED, &[2], records(2, b"abcd")).err(),
Some(corrupt())
);
}
#[test]
fn the_header_must_be_one_positive_varint() {
let input = Input::serial(&[1]);
for header in [&[][..], &[0], &[0x80], &[2, 0], &[0x80, 0x00]] {
assert_eq!(
run(&DESC, header, input).err(),
Some(corrupt()),
"{header:?}"
);
}
}
#[test]
fn counts_beyond_the_budget_are_corrupt() {
let mut huge = alloc::vec![0xff; 9];
huge.push(1);
assert_eq!(
run(&DESC, &huge, Input::serial(&[1])).err(),
Some(corrupt())
);
assert_eq!(
run(&CONSTANT_FIXED, &[0xff, 0xff, 0x03], records(64, &[0; 64])).err(),
Some(corrupt())
);
}
}