use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
pub(crate) const TRANSPOSE: Desc = Desc {
id: 2,
min_version: 3,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[Type::Struct],
variable_inputs: 0,
decode: transpose,
};
pub(crate) const TRANSPOSE_SPLIT: Desc = Desc {
id: 4,
min_version: 11,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[],
variable_inputs: Type::Serial.bit(),
decode: transpose_split,
};
pub(crate) const TRANSPOSE_SPLIT2: Desc = Desc {
id: 30,
min_version: 3,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[Type::Serial; 2],
variable_inputs: 0,
decode: transpose_split,
};
pub(crate) const TRANSPOSE_SPLIT4: Desc = Desc {
id: 31,
min_version: 3,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[Type::Serial; 4],
variable_inputs: 0,
decode: transpose_split,
};
pub(crate) const TRANSPOSE_SPLIT8: Desc = Desc {
id: 32,
min_version: 3,
regens: &[Type::Struct],
variable_regens: false,
inputs: &[Type::Serial; 8],
variable_inputs: 0,
decode: transpose_split,
};
fn interleave(out: &mut alloc::vec::Vec<u8>, columns: &[&[u8]], count: usize) {
let width = columns.len();
let start = out.len();
out.resize(start + count * width, 0);
let dst = &mut out[start..];
match *columns {
[a, b] => interleave_fixed(dst, [a, b], count),
[a, b, c, d] => interleave_fixed(dst, [a, b, c, d], count),
[a, b, c, d, e, f, g, h] => interleave_fixed(dst, [a, b, c, d, e, f, g, h], count),
_ => {
for (pos, column) in columns.iter().enumerate() {
for (element, &byte) in dst.chunks_exact_mut(width).zip(&column[..count]) {
element[pos] = byte;
}
}
}
}
}
fn interleave_fixed<const W: usize>(dst: &mut [u8], columns: [&[u8]; W], count: usize) {
let columns = columns.map(|column| &column[..count]);
for (index, element) in dst.as_chunks_mut::<W>().0.iter_mut().enumerate() {
for (byte, column) in element.iter_mut().zip(&columns) {
*byte = column[index];
}
}
}
fn transpose(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let input = &inputs[0];
let count = input.len();
let width = input.width;
let (new_count, new_width) = if count == 0 {
(0, width)
} else {
(width, count)
};
let out = regens[0].fixed(new_width, new_count)?;
if new_count == 0 {
return Ok(());
}
let bytes = &input.bytes[..count * width];
let columns: alloc::vec::Vec<&[u8]> = bytes.chunks_exact(new_count).collect();
interleave(out, &columns, new_count);
Ok(())
}
fn transpose_split(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let Some(first) = inputs.first() else {
return Err(corrupt());
};
let count = first.len();
let mut columns = alloc::vec::Vec::with_capacity(inputs.len());
for input in inputs {
if input.len() != count {
return Err(corrupt());
}
columns.push(&input.bytes[..count]);
}
let out = regens[0].fixed(columns.len(), count)?;
interleave(out, &columns, count);
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::State;
use super::*;
use crate::Backend;
use alloc::vec::Vec;
fn run(desc: &Desc, inputs: &[Input<'_>]) -> Result<Stream, DecodeError> {
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, &[]);
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 structs(width: usize, bytes: &[u8]) -> Input<'_> {
Input {
ty: Type::Struct,
width,
bytes,
lens: &[],
}
}
fn transposed(bytes: &[u8], width: usize) -> Vec<Vec<u8>> {
(0..width)
.map(|p| bytes.chunks_exact(width).map(|e| e[p]).collect())
.collect()
}
#[test]
fn split_columns_interleave_for_every_width() {
for width in 1..=11usize {
for count in [0usize, 1, 2, 3, 31, 100] {
let bytes: Vec<u8> = (0..width * count).map(|i| (i * 7 + 3) as u8).collect();
let columns = transposed(&bytes, width);
let inputs: Vec<Input<'_>> = columns.iter().map(|c| Input::serial(c)).collect();
let out = run(&TRANSPOSE_SPLIT, &inputs).unwrap();
assert_eq!(out.bytes(), bytes, "{width} x {count}");
assert_eq!(out.as_input().width, width);
assert_eq!(out.as_input().len(), count);
}
}
}
#[test]
fn legacy_variants_take_a_fixed_number_of_columns() {
for (desc, width) in [
(&TRANSPOSE_SPLIT2, 2usize),
(&TRANSPOSE_SPLIT4, 4),
(&TRANSPOSE_SPLIT8, 8),
] {
let bytes: Vec<u8> = (0..width * 9).map(|i| i as u8).collect();
let columns = transposed(&bytes, width);
let inputs: Vec<Input<'_>> = columns.iter().map(|c| Input::serial(c)).collect();
assert_eq!(run(desc, &inputs).unwrap().bytes(), bytes);
assert_eq!(desc.inputs.len(), width);
}
}
#[test]
fn mismatched_or_missing_columns_are_corrupt() {
assert_eq!(run(&TRANSPOSE_SPLIT, &[]).map(|_| ()), Err(corrupt()));
let (a, b) = ([1u8, 2], [3u8]);
assert_eq!(
run(&TRANSPOSE_SPLIT, &[Input::serial(&a), Input::serial(&b)]).map(|_| ()),
Err(corrupt())
);
}
#[test]
fn transpose_turns_columns_back_into_elements() {
for width in [1usize, 2, 3, 4, 8, 9] {
for count in [0usize, 1, 5, 40] {
let original: Vec<u8> = (0..width * count).map(|i| (i * 13 + 1) as u8).collect();
let columns: Vec<u8> = transposed(&original, width).concat();
let input = if count == 0 {
structs(width, &[])
} else {
structs(count, &columns)
};
let out = run(&TRANSPOSE, &[input]).unwrap();
assert_eq!(out.bytes(), original, "{width} x {count}");
if count > 0 {
assert_eq!(out.as_input().width, width);
} else {
assert_eq!(out.as_input().len(), 0);
}
}
}
}
#[test]
fn transposed_output_is_limited_by_the_budget() {
let bytes = [0u8; 64];
let mut state = State::new(Backend::default());
let mut ctx = Ctx::new(&mut state, &[]);
let mut regens = [Stream::default()];
regens[0].prepare(Type::Struct, 32);
assert_eq!(
(TRANSPOSE.decode)(&mut ctx, &[structs(8, &bytes)], &mut regens),
Err(corrupt())
);
}
}