use super::{Ctx, Desc};
use crate::decompressor::core::data::{Input, Stream, Type, corrupt};
use crate::decompressor::error::DecodeError;
const MAX_TAGGED_STREAMS: usize = 1 << 16;
const MAX_STRING_STREAMS: usize = 2048;
pub(crate) const DISPATCH_N_BY_TAG: Desc = Desc {
id: 42,
min_version: 9,
regens: &[Type::Serial],
variable_regens: false,
inputs: &[Type::Numeric, Type::Numeric],
variable_inputs: Type::Serial.bit(),
decode: by_tag,
};
pub(crate) const DISPATCH_STRING: Desc = Desc {
id: 54,
min_version: 16,
regens: &[Type::String],
variable_regens: false,
inputs: &[Type::Numeric],
variable_inputs: Type::String.bit(),
decode: by_index,
};
fn read_le(bytes: &[u8]) -> u64 {
let mut word = [0u8; 8];
word[..bytes.len()].copy_from_slice(bytes);
u64::from_le_bytes(word)
}
fn by_tag(_: &mut Ctx<'_>, inputs: &[Input<'_>], regens: &mut [Stream]) -> Result<(), DecodeError> {
let (tags, sizes, streams) = (&inputs[0], &inputs[1], &inputs[2..]);
let segments = sizes.len();
if tags.len() != segments || tags.width > 2 || streams.len() >= MAX_TAGGED_STREAMS {
return Err(corrupt());
}
let mut total = 0usize;
for stream in streams {
total = total.checked_add(stream.len()).ok_or_else(corrupt)?;
}
let tag_of = |segment: usize| read_le(&tags.bytes[segment * tags.width..][..tags.width]);
let size_of = |segment: usize| read_le(&sizes.bytes[segment * sizes.width..][..sizes.width]);
let mut used = alloc::vec![0usize; streams.len()];
for segment in 0..segments {
let tag = usize::try_from(tag_of(segment)).ok().ok_or_else(corrupt)?;
let size = usize::try_from(size_of(segment)).ok().ok_or_else(corrupt)?;
let slot = used.get_mut(tag).ok_or_else(corrupt)?;
*slot = slot.checked_add(size).ok_or_else(corrupt)?;
}
if used.iter().zip(streams).any(|(&used, s)| used != s.len()) {
return Err(corrupt());
}
let out = regens[0].fixed(1, total)?;
let mut cursors = alloc::vec![0usize; streams.len()];
for segment in 0..segments {
let (tag, size) = (tag_of(segment) as usize, size_of(segment) as usize);
let cursor = &mut cursors[tag];
out.extend_from_slice(&streams[tag].bytes[*cursor..*cursor + size]);
*cursor += size;
}
Ok(())
}
fn by_index(
_: &mut Ctx<'_>,
inputs: &[Input<'_>],
regens: &mut [Stream],
) -> Result<(), DecodeError> {
let (indices, streams) = (&inputs[0], &inputs[1..]);
if indices.width != 2 || streams.len() > MAX_STRING_STREAMS {
return Err(corrupt());
}
let count = indices.len();
if count != 0 && streams.is_empty() {
return Err(corrupt());
}
let index_of = |string: usize| usize::from(read_le(&indices.bytes[string * 2..][..2]) as u16);
let mut routed = alloc::vec![0usize; streams.len()];
for string in 0..count {
*routed.get_mut(index_of(string)).ok_or_else(corrupt)? += 1;
}
let mut total = 0usize;
for (stream, &routed) in streams.iter().zip(&routed) {
if stream.len() != routed {
return Err(corrupt());
}
total = total.checked_add(stream.bytes.len()).ok_or_else(corrupt)?;
}
let (bytes, lens) = regens[0].strings(count, total)?;
let mut cursors = alloc::vec![(0usize, 0usize); streams.len()];
for string in 0..count {
let index = index_of(string);
let (byte, next) = &mut cursors[index];
let len = streams[index].lens[*next];
let end = byte.checked_add(len as usize).ok_or_else(corrupt)?;
bytes.extend_from_slice(streams[index].bytes.get(*byte..end).ok_or_else(corrupt)?);
lens.push(len);
*byte = end;
*next += 1;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::State;
use super::*;
use crate::Backend;
use alloc::vec::Vec;
fn run(desc: &Desc, inputs: &[Input<'_>], budget: usize) -> 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), budget);
(desc.decode)(&mut ctx, inputs, &mut regens)?;
let [stream] = regens;
stream.validate()?;
Ok(stream)
}
fn numbers(width: usize, bytes: &[u8]) -> Input<'_> {
Input {
ty: Type::Numeric,
width,
bytes,
lens: &[],
}
}
fn strings<'a>(bytes: &'a [u8], lens: &'a [u32]) -> Input<'a> {
Input {
ty: Type::String,
width: 1,
bytes,
lens,
}
}
#[test]
fn tagged_segments_are_woven_back() {
let (tags, sizes) = ([1u8, 0, 1, 0], [2u8, 1, 3, 1]);
let out = run(
&DISPATCH_N_BY_TAG,
&[
numbers(1, &tags),
numbers(1, &sizes),
Input::serial(b"cg"),
Input::serial(b"abdef"),
],
1 << 10,
)
.unwrap();
assert_eq!(out.bytes(), b"abcdefg");
let tags: Vec<u8> = [0u16, 1].iter().flat_map(|t| t.to_le_bytes()).collect();
let sizes: Vec<u8> = [1u32, 1].iter().flat_map(|t| t.to_le_bytes()).collect();
let out = run(
&DISPATCH_N_BY_TAG,
&[
numbers(2, &tags),
numbers(4, &sizes),
Input::serial(b"x"),
Input::serial(b"y"),
],
1 << 10,
)
.unwrap();
assert_eq!(out.bytes(), b"xy");
let out = run(&DISPATCH_N_BY_TAG, &[numbers(1, &[]), numbers(1, &[])], 8).unwrap();
assert_eq!(out.bytes(), b"");
}
#[test]
fn malformed_tagged_segments_are_corrupt() {
let a = Input::serial(b"ab");
type Case<'a> = (&'a [u8], &'a [u8], usize, &'a [Input<'a>]);
let cases: [Case<'_>; 5] = [
(&[1], &[2], 1, &[a]),
(&[0], &[1], 1, &[a]),
(&[0], &[3], 1, &[a]),
(&[0, 0], &[2], 1, &[a]),
(&[0, 0, 0, 0], &[2], 4, &[a]),
];
for (tags, sizes, tag_width, streams) in cases {
let mut inputs = alloc::vec![numbers(tag_width, tags), numbers(1, sizes)];
inputs.extend_from_slice(streams);
assert_eq!(
run(&DISPATCH_N_BY_TAG, &inputs, 1 << 10).map(|_| ()),
Err(corrupt()),
"{tags:?} {sizes:?}"
);
}
let sizes: Vec<u8> = [u64::MAX, 2].iter().flat_map(|s| s.to_le_bytes()).collect();
assert_eq!(
run(
&DISPATCH_N_BY_TAG,
&[numbers(1, &[0, 0]), numbers(8, &sizes), a],
1 << 10
)
.map(|_| ()),
Err(corrupt())
);
assert_eq!(
run(
&DISPATCH_N_BY_TAG,
&[numbers(1, &[0]), numbers(1, &[2]), a],
1
)
.map(|_| ()),
Err(corrupt())
);
}
#[test]
fn strings_are_routed_by_index() {
let indices: Vec<u8> = [1u16, 0, 1, 1, 0]
.iter()
.flat_map(|i| i.to_le_bytes())
.collect();
let out = run(
&DISPATCH_STRING,
&[
numbers(2, &indices),
strings(b"bdd", &[1, 2]),
strings(b"aceee", &[1, 1, 3]),
],
1 << 10,
)
.unwrap();
assert_eq!(out.bytes(), b"abceeedd");
assert_eq!(out.as_input().lens, [1, 1, 1, 3, 2]);
let one = 0u16.to_le_bytes();
let out = run(
&DISPATCH_STRING,
&[numbers(2, &one), strings(b"", &[0])],
1 << 10,
)
.unwrap();
assert_eq!(out.as_input().lens, [0]);
let out = run(&DISPATCH_STRING, &[numbers(2, &[]), strings(b"", &[])], 8).unwrap();
assert_eq!(out.as_input().len(), 0);
let out = run(&DISPATCH_STRING, &[numbers(2, &[])], 8).unwrap();
assert_eq!(out.as_input().len(), 0);
}
#[test]
fn malformed_string_dispatches_are_corrupt() {
let (zero, one) = (0u16.to_le_bytes(), [1u8, 0]);
let a = strings(b"a", &[1]);
type Case<'a> = (Input<'a>, &'a [Input<'a>]);
let cases: [Case<'_>; 6] = [
(numbers(1, &[0]), &[a]),
(numbers(2, &zero), &[]),
(numbers(2, &one), &[a]),
(numbers(2, &[]), &[a]),
(numbers(2, &zero), &[strings(b"", &[])]),
(numbers(2, &zero), &[strings(b"", &[0, 0])]),
];
for (indices, streams) in cases {
let mut inputs = alloc::vec![indices];
inputs.extend_from_slice(streams);
assert_eq!(
run(&DISPATCH_STRING, &inputs, 1 << 10).map(|_| ()),
Err(corrupt()),
"{inputs:?}"
);
}
let mut inputs = alloc::vec![numbers(2, &[])];
inputs.extend(core::iter::repeat_n(
strings(b"", &[]),
MAX_STRING_STREAMS + 1,
));
assert_eq!(
run(&DISPATCH_STRING, &inputs, 1 << 20).map(|_| ()),
Err(corrupt())
);
assert_eq!(
run(&DISPATCH_STRING, &[numbers(2, &zero), a], 4).map(|_| ()),
Err(corrupt())
);
}
}