use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
use crate::shared::varint;
macro_rules! splitn {
($id:expr, $min_version:expr, $ty:expr) => {
Desc {
id: $id,
min_version: $min_version,
regens: &[$ty],
variable_regens: false,
inputs: &[],
variable_inputs: $ty.bit(),
decode: join,
}
};
}
pub(crate) const SPLITN: Desc = splitn!(40, 9, Type::Serial);
pub(crate) const SPLITN_STRUCT: Desc = splitn!(47, 14, Type::Struct);
pub(crate) const SPLITN_NUM: Desc = splitn!(48, 14, Type::Numeric);
fn join(ctx: &mut Ctx<'_>, inputs: &[Input<'_>], regens: &mut [Stream]) -> Result<(), DecodeError> {
let width = match inputs.first() {
Some(first) => first.width,
None if ctx.header.is_empty() => 1,
None => {
let (width, _) = varint::decode(ctx.header).map_err(|_| corrupt())?;
usize::try_from(width).ok().ok_or_else(corrupt)?
}
};
if regens[0].ty() == Type::Serial && width != 1 {
return Err(corrupt());
}
let mut count = 0usize;
for input in inputs {
if input.width != width {
return Err(corrupt());
}
count = count.checked_add(input.len()).ok_or_else(corrupt)?;
}
let out = regens[0].fixed(width, count)?;
for input in inputs {
out.extend_from_slice(input.bytes);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::State;
use super::*;
use crate::Backend;
fn run(desc: &Desc, header: &[u8], inputs: &[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, inputs, &mut regens)?;
let [stream] = regens;
stream.validate()?;
Ok(stream)
}
fn typed(ty: Type, width: usize, bytes: &[u8]) -> Input<'_> {
Input {
ty,
width,
bytes,
lens: &[],
}
}
#[test]
fn segments_are_joined_in_order() {
let out = run(
&SPLITN,
&[],
&[
Input::serial(b"ab"),
Input::serial(b""),
Input::serial(b"cde"),
],
)
.unwrap();
assert_eq!(out.bytes(), b"abcde");
assert_eq!(out.ty(), Type::Serial);
let (a, b) = ([1u8, 0, 2, 0], [3u8, 0]);
let out = run(
&SPLITN_NUM,
&[],
&[typed(Type::Numeric, 2, &a), typed(Type::Numeric, 2, &b)],
)
.unwrap();
assert_eq!(
(out.bytes(), out.as_input().width),
(&[1u8, 0, 2, 0, 3, 0][..], 2)
);
let out = run(
&SPLITN_STRUCT,
&[],
&[typed(Type::Struct, 3, &[1, 2, 3, 4, 5, 6])],
)
.unwrap();
assert_eq!((out.as_input().len(), out.as_input().width), (2, 3));
}
#[test]
fn widths_must_agree() {
let bytes = [0u8; 4];
assert_eq!(
run(
&SPLITN_NUM,
&[],
&[
typed(Type::Numeric, 2, &bytes),
typed(Type::Numeric, 4, &bytes)
]
)
.map(|_| ()),
Err(corrupt())
);
}
#[test]
fn no_segment_takes_the_width_from_the_header() {
let out = run(&SPLITN, &[], &[]).unwrap();
assert_eq!((out.as_input().len(), out.as_input().width), (0, 1));
assert_eq!(run(&SPLITN, &[4], &[]).map(|_| ()), Err(corrupt()));
assert!(run(&SPLITN, &[1], &[]).is_ok());
let out = run(&SPLITN_STRUCT, &[7], &[]).unwrap();
assert_eq!((out.as_input().len(), out.as_input().width), (0, 7));
let out = run(&SPLITN_NUM, &[4, 99], &[]).unwrap();
assert_eq!(out.as_input().width, 4);
for header in [&[0x80u8][..], &[0], &[3]] {
let desc = if header == [3] {
&SPLITN_NUM
} else {
&SPLITN_STRUCT
};
assert_eq!(
run(desc, header, &[]).map(|_| ()),
Err(corrupt()),
"{header:?}"
);
}
}
#[test]
fn output_is_limited_by_the_budget() {
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, &[]);
let mut regens = [Stream::default()];
regens[0].prepare(Type::Serial, 3);
assert_eq!(
(SPLITN.decode)(&mut ctx, &[Input::serial(b"abcd")], &mut regens),
Err(corrupt())
);
}
}