use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
macro_rules! concat {
($id:expr, $min_version:expr, $ty:expr) => {
Desc {
id: $id,
min_version: $min_version,
regens: &[$ty],
variable_regens: true,
inputs: &[Type::Numeric, $ty],
variable_inputs: 0,
decode: split,
}
};
}
pub(crate) const CONCAT_SERIAL: Desc = concat!(55, 16, Type::Serial);
pub(crate) const CONCAT_NUM: Desc = concat!(57, 17, Type::Numeric);
pub(crate) const CONCAT_STRUCT: Desc = concat!(58, 17, Type::Struct);
pub(crate) const CONCAT_STRING: Desc = concat!(59, 18, Type::String);
fn split(_: &mut Ctx<'_>, inputs: &[Input<'_>], regens: &mut [Stream]) -> Result<(), DecodeError> {
let (sizes, whole) = (&inputs[0], &inputs[1]);
if sizes.width != 4 || sizes.len() == 0 || sizes.len() != regens.len() {
return Err(corrupt());
}
let sizes = sizes
.bytes
.as_chunks::<4>()
.0
.iter()
.map(|&size| u32::from_le_bytes(size) as usize);
let total = whole.len();
let mut first = 0usize;
if whole.ty == Type::String {
let mut byte = 0usize;
for (size, regen) in sizes.zip(regens) {
let end = first.checked_add(size).filter(|&end| end <= total);
let lens = &whole.lens[first..end.ok_or_else(corrupt)?];
let len = lens.iter().map(|&len| len as usize).sum::<usize>();
let (bytes, out_lens) = regen.strings(size, len)?;
bytes.extend_from_slice(&whole.bytes[byte..byte + len]);
out_lens.extend_from_slice(lens);
first += size;
byte += len;
}
} else {
for (size, regen) in sizes.zip(regens) {
let end = first.checked_add(size).filter(|&end| end <= total);
let end = end.ok_or_else(corrupt)?;
regen
.fixed(whole.width, size)?
.extend_from_slice(&whole.bytes[first * whole.width..end * whole.width]);
first = end;
}
}
if first != total {
return Err(corrupt());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::State;
use super::*;
use crate::Backend;
use alloc::vec::Vec;
fn run(desc: &Desc, inputs: &[Input<'_>], outputs: usize) -> Result<Vec<Stream>, DecodeError> {
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, &[]);
let mut regens: Vec<Stream> = (0..outputs).map(|_| Stream::default()).collect();
for (n, stream) in regens.iter_mut().enumerate() {
stream.prepare(desc.regen_type(n), 1 << 16);
}
(desc.decode)(&mut ctx, inputs, &mut regens)?;
regens.iter().try_for_each(Stream::validate)?;
Ok(regens)
}
fn sizes(values: &[u32]) -> Vec<u8> {
values.iter().flat_map(|v| v.to_le_bytes()).collect()
}
fn typed(ty: Type, width: usize, bytes: &[u8]) -> Input<'_> {
Input {
ty,
width,
bytes,
lens: &[],
}
}
#[test]
fn serial_slices_follow_the_sizes() {
let sizes = sizes(&[2, 0, 3]);
let out = run(
&CONCAT_SERIAL,
&[typed(Type::Numeric, 4, &sizes), Input::serial(b"abcde")],
3,
)
.unwrap();
let contents: Vec<&[u8]> = out.iter().map(Stream::bytes).collect();
assert_eq!(contents, [&b"ab"[..], b"", b"cde"]);
}
#[test]
fn numeric_and_struct_slices_keep_their_width() {
let sizes = sizes(&[1, 2]);
let bytes = [1u8, 0, 2, 0, 3, 0];
let out = run(
&CONCAT_NUM,
&[
typed(Type::Numeric, 4, &sizes),
typed(Type::Numeric, 2, &bytes),
],
2,
)
.unwrap();
assert_eq!(out[0].bytes(), [1, 0]);
assert_eq!(
(out[1].bytes(), out[1].as_input().width),
(&[2u8, 0, 3, 0][..], 2)
);
let out = run(
&CONCAT_STRUCT,
&[
typed(Type::Numeric, 4, &sizes),
typed(Type::Struct, 2, &bytes),
],
2,
)
.unwrap();
assert_eq!(out[1].as_input().len(), 2);
}
#[test]
fn string_slices_split_the_lengths_too() {
let sizes = sizes(&[2, 0, 1]);
let lens = [1u32, 2, 3];
let whole = Input {
ty: Type::String,
width: 1,
bytes: b"abbccc",
lens: &lens,
};
let out = run(&CONCAT_STRING, &[typed(Type::Numeric, 4, &sizes), whole], 3).unwrap();
assert_eq!(out[0].bytes(), b"abb");
assert_eq!(out[0].as_input().lens, [1, 2]);
assert_eq!(out[1].as_input().len(), 0);
assert_eq!(out[2].as_input().lens, [3]);
}
#[test]
fn inconsistent_sizes_are_corrupt() {
let one = sizes(&[3]);
let data = Input::serial(b"abcd");
assert_eq!(
run(&CONCAT_SERIAL, &[typed(Type::Numeric, 4, &one), data], 1).map(|_| ()),
Err(corrupt())
);
let big = sizes(&[5]);
assert_eq!(
run(&CONCAT_SERIAL, &[typed(Type::Numeric, 4, &big), data], 1).map(|_| ()),
Err(corrupt())
);
let huge = sizes(&[u32::MAX, u32::MAX]);
assert_eq!(
run(&CONCAT_SERIAL, &[typed(Type::Numeric, 4, &huge), data], 2).map(|_| ()),
Err(corrupt())
);
let none = [0u8; 0];
assert_eq!(
run(
&CONCAT_SERIAL,
&[typed(Type::Numeric, 4, &none), Input::serial(b"")],
1
)
.map(|_| ()),
Err(corrupt())
);
let wide = [4u8, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
run(&CONCAT_SERIAL, &[typed(Type::Numeric, 8, &wide), data], 1).map(|_| ()),
Err(corrupt())
);
let two = sizes(&[2, 2]);
for outputs in [1, 3] {
assert_eq!(
run(
&CONCAT_SERIAL,
&[typed(Type::Numeric, 4, &two), data],
outputs
)
.map(|_| ()),
Err(corrupt())
);
}
let lens = [1u32, 1];
let whole = Input {
ty: Type::String,
width: 1,
bytes: b"ab",
lens: &lens,
};
for bad in [sizes(&[3]), sizes(&[1])] {
assert_eq!(
run(&CONCAT_STRING, &[typed(Type::Numeric, 4, &bad), whole], 1).map(|_| ()),
Err(corrupt())
);
}
}
}