use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
use crate::shared::varint;
const fn conversion(
id: u32,
min_version: u32,
regen: &'static [Type],
inputs: &'static [Type],
decode: super::DecodeFn,
) -> Desc {
Desc {
id,
min_version,
regens: regen,
variable_regens: false,
inputs,
variable_inputs: 0,
decode,
}
}
pub(crate) const SERIAL_TO_STRUCT: Desc =
conversion(5, 3, &[Type::Serial], &[Type::Struct], to_bytes);
pub(crate) const STRUCT_TO_SERIAL: Desc =
conversion(6, 3, &[Type::Struct], &[Type::Serial], serial_to_struct);
pub(crate) const STRUCT_TO_NUM_LE: Desc =
conversion(7, 3, &[Type::Struct], &[Type::Numeric], same_width);
pub(crate) const NUM_TO_STRUCT_LE: Desc =
conversion(8, 3, &[Type::Numeric], &[Type::Struct], same_width);
pub(crate) const SERIAL_TO_NUM_LE: Desc =
conversion(9, 3, &[Type::Serial], &[Type::Numeric], to_bytes);
pub(crate) const NUM_TO_SERIAL_LE: Desc =
conversion(10, 3, &[Type::Numeric], &[Type::Serial], serial_to_num);
pub(crate) const SERIAL_STRING: Desc =
conversion(11, 10, &[Type::Serial], &[Type::String], to_bytes);
pub(crate) const SEPARATE_STRING_COMPONENTS: Desc = conversion(
12,
10,
&[Type::String],
&[Type::Serial, Type::Numeric],
join_strings,
);
pub(crate) const STRUCT_TO_NUM_BE: Desc =
conversion(13, 21, &[Type::Struct], &[Type::Numeric], swapped);
pub(crate) const SERIAL_TO_NUM_BE: Desc =
conversion(14, 21, &[Type::Serial], &[Type::Numeric], swapped);
fn to_bytes(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let bytes = inputs[0].bytes;
regens[0].fixed(1, bytes.len())?.extend_from_slice(bytes);
Ok(())
}
fn same_width(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
regens[0].copy_of(&inputs[0])
}
fn swapped(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let input = &inputs[0];
let width = if regens[0].ty() == Type::Serial {
1
} else {
input.width
};
let out = regens[0].fixed(width, input.bytes.len() / width)?;
for element in input.bytes.chunks_exact(input.width) {
out.extend(element.iter().rev());
}
Ok(())
}
fn serial_to_struct(
ctx: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let (width, len) = varint::decode(ctx.header).or(Err(corrupt()))?;
if width == 0 || len != ctx.header.len() {
return Err(corrupt());
}
let width = usize::try_from(width).or(Err(corrupt()))?;
let bytes = inputs[0].bytes;
if !bytes.len().is_multiple_of(width) {
return Err(corrupt());
}
regens[0]
.fixed(width, bytes.len() / width)?
.extend_from_slice(bytes);
Ok(())
}
fn serial_to_num(
ctx: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let &[log] = ctx.header else {
return Err(corrupt());
};
if log > 3 {
return Err(corrupt());
}
let width = 1usize << log;
let bytes = inputs[0].bytes;
if !bytes.len().is_multiple_of(width) {
return Err(corrupt());
}
regens[0]
.fixed(width, bytes.len() / width)?
.extend_from_slice(bytes);
Ok(())
}
fn join_strings(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let (contents, sizes) = (&inputs[0], &inputs[1]);
let (bytes, lens) = regens[0].strings(sizes.len(), contents.bytes.len())?;
let mut total = 0u64;
for size in sizes.bytes.chunks_exact(sizes.width) {
let mut value = [0u8; 8];
value[..size.len()].copy_from_slice(size);
let len = u32::try_from(u64::from_le_bytes(value)).or(Err(corrupt()))?;
total += u64::from(len);
lens.push(len);
}
if total != contents.bytes.len() as u64 {
return Err(corrupt());
}
bytes.extend_from_slice(contents.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 descriptors_are_built_from_their_signature() {
let desc = conversion(5, 3, &[Type::Serial], &[Type::Struct], to_bytes);
assert_eq!(
(desc.id, desc.min_version),
(SERIAL_TO_STRUCT.id, SERIAL_TO_STRUCT.min_version)
);
assert_eq!(
(desc.regens, desc.inputs),
(SERIAL_TO_STRUCT.regens, SERIAL_TO_STRUCT.inputs)
);
assert!(!desc.variable_regens && desc.variable_inputs == 0);
}
#[test]
fn retagging_conversions_keep_bytes_and_width() {
let bytes = [1u8, 2, 3, 4];
let structs = run(&SERIAL_TO_STRUCT, &[], &[typed(Type::Struct, 2, &bytes)]).unwrap();
assert_eq!((structs.ty(), structs.as_input().width), (Type::Serial, 1));
let numbers = run(&NUM_TO_STRUCT_LE, &[], &[typed(Type::Struct, 4, &bytes)]).unwrap();
assert_eq!((numbers.ty(), numbers.as_input().width), (Type::Numeric, 4));
let odd = [0u8; 3];
assert!(run(&NUM_TO_STRUCT_LE, &[], &[typed(Type::Struct, 3, &odd)]).is_err());
let back = run(&STRUCT_TO_NUM_LE, &[], &[typed(Type::Numeric, 2, &bytes)]).unwrap();
assert_eq!((back.ty(), back.as_input().width), (Type::Struct, 2));
let serial = run(&SERIAL_TO_NUM_LE, &[], &[typed(Type::Numeric, 2, &bytes)]).unwrap();
assert_eq!(serial.bytes(), bytes);
let lens = [1u32, 3];
let strings = Input {
ty: Type::String,
width: 1,
bytes: &bytes,
lens: &lens,
};
assert_eq!(run(&SERIAL_STRING, &[], &[strings]).unwrap().bytes(), bytes);
}
#[test]
fn big_endian_conversions_swap_each_element() {
let bytes = [1u8, 2, 3, 4];
let serial = run(&SERIAL_TO_NUM_BE, &[], &[typed(Type::Numeric, 2, &bytes)]).unwrap();
assert_eq!(
(serial.bytes(), serial.as_input().width),
(&[2u8, 1, 4, 3][..], 1)
);
let structs = run(&STRUCT_TO_NUM_BE, &[], &[typed(Type::Numeric, 4, &bytes)]).unwrap();
assert_eq!(
(structs.bytes(), structs.as_input().width),
(&[4u8, 3, 2, 1][..], 4)
);
}
#[test]
fn widths_come_from_the_header() {
let bytes = [0u8; 6];
let serial = typed(Type::Serial, 1, &bytes);
let structs = run(&STRUCT_TO_SERIAL, &[3], &[serial]).unwrap();
assert_eq!(structs.as_input().len(), 2);
for header in [&[][..], &[0], &[4], &[3, 0], &[0x80]] {
assert!(
run(&STRUCT_TO_SERIAL, header, &[serial]).is_err(),
"{header:?}"
);
}
let numbers = run(&NUM_TO_SERIAL_LE, &[1], &[serial]).unwrap();
assert_eq!(numbers.as_input().len(), 3);
for header in [&[][..], &[2], &[4], &[1, 1]] {
assert!(
run(&NUM_TO_SERIAL_LE, header, &[serial]).is_err(),
"{header:?}"
);
}
}
#[test]
fn strings_join_contents_and_lengths() {
let sizes: alloc::vec::Vec<u8> =
[2u64, 0, 3].iter().flat_map(|v| v.to_le_bytes()).collect();
let joined = run(
&SEPARATE_STRING_COMPONENTS,
&[],
&[Input::serial(b"abcde"), typed(Type::Numeric, 8, &sizes)],
)
.unwrap();
assert_eq!(joined.as_input().lens, [2, 0, 3]);
assert_eq!(joined.bytes(), b"abcde");
assert!(
run(
&SEPARATE_STRING_COMPONENTS,
&[],
&[Input::serial(b"abcd"), typed(Type::Numeric, 8, &sizes)]
)
.is_err()
);
let huge = (1u64 << 32).to_le_bytes();
assert!(
run(
&SEPARATE_STRING_COMPONENTS,
&[],
&[Input::serial(b""), typed(Type::Numeric, 8, &huge)]
)
.is_err()
);
}
}