use alloc::vec::Vec;
use crate::decompressor::error::{DecodeError, InvalidDataKind};
pub(crate) use crate::shared::wire::StreamType as Type;
pub(crate) const fn corrupt() -> DecodeError {
DecodeError::invalid(InvalidDataKind::CodecStream)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Input<'a> {
pub(crate) ty: Type,
pub(crate) width: usize,
pub(crate) bytes: &'a [u8],
pub(crate) lens: &'a [u32],
}
impl<'a> Input<'a> {
pub(crate) const fn serial(bytes: &'a [u8]) -> Self {
Self {
ty: Type::Serial,
width: 1,
bytes,
lens: &[],
}
}
#[cfg(any(
test,
feature = "bitsplit",
feature = "bitunpack",
feature = "concat",
feature = "constant",
feature = "conversion",
feature = "dedup",
feature = "delta",
feature = "dispatch",
feature = "divide-by",
feature = "float-deconstruct",
feature = "lz",
feature = "merge-sorted",
feature = "mux-lengths",
feature = "parse-int",
feature = "prefix",
feature = "range-pack",
feature = "sentinel",
feature = "sparse-num",
feature = "split",
feature = "split-by-struct",
feature = "tokenize",
feature = "transpose",
feature = "zigzag"
))]
pub(crate) const fn len(&self) -> usize {
match self.ty {
Type::String => self.lens.len(),
_ => self.bytes.len() / self.width,
}
}
}
#[derive(Debug, Default)]
pub(crate) struct Stream {
ty: Type,
width: usize,
bytes: Vec<u8>,
lens: Vec<u32>,
ready: bool,
budget: usize,
}
impl Stream {
pub(crate) fn prepare(&mut self, ty: Type, budget: usize) {
self.ty = ty;
self.width = 0;
self.bytes.clear();
self.lens.clear();
self.ready = false;
self.budget = budget;
}
pub(crate) const fn ty(&self) -> Type {
self.ty
}
#[cfg(any(
test,
feature = "bitpack",
feature = "bitsplit",
feature = "bitunpack",
feature = "concat",
feature = "constant",
feature = "conversion",
feature = "dedup",
feature = "delta",
feature = "dispatch",
feature = "divide-by",
feature = "entropy",
feature = "field-lz",
feature = "flatpack",
feature = "float-deconstruct",
feature = "lz",
feature = "lz4",
feature = "merge-sorted",
feature = "mux-lengths",
feature = "partition",
feature = "pivco-huffman",
feature = "quantize",
feature = "range-pack",
feature = "sentinel",
feature = "sparse-num",
feature = "split",
feature = "split-by-struct",
feature = "tokenize",
feature = "transpose",
feature = "zigzag",
feature = "zstd"
))]
pub(crate) fn fixed(
&mut self,
width: usize,
count: usize,
) -> Result<&mut Vec<u8>, DecodeError> {
let bytes = width.checked_mul(count).ok_or_else(corrupt)?;
let valid = match self.ty {
Type::Serial => width == 1,
Type::Numeric => matches!(width, 1 | 2 | 4 | 8),
Type::Struct => width != 0,
Type::String => false,
};
if !valid || bytes > self.budget {
return Err(corrupt());
}
self.width = width;
self.bytes.clear();
self.bytes.try_reserve(bytes).or(Err(corrupt()))?;
self.ready = true;
Ok(&mut self.bytes)
}
#[cfg(any(
test,
feature = "concat",
feature = "conversion",
feature = "dedup",
feature = "dispatch",
feature = "interleave",
feature = "parse-int",
feature = "prefix",
feature = "tokenize"
))]
pub(crate) fn strings(
&mut self,
count: usize,
total: usize,
) -> Result<(&mut Vec<u8>, &mut Vec<u32>), DecodeError> {
let need = count
.checked_mul(4)
.and_then(|lens| lens.checked_add(total))
.ok_or_else(corrupt)?;
if self.ty != Type::String || need > self.budget {
return Err(corrupt());
}
self.width = 1;
self.bytes.clear();
self.lens.clear();
self.bytes.try_reserve(total).or(Err(corrupt()))?;
self.lens.try_reserve(count).or(Err(corrupt()))?;
self.ready = true;
Ok((&mut self.bytes, &mut self.lens))
}
#[cfg(any(test, feature = "conversion", feature = "dedup"))]
pub(crate) fn copy_of(&mut self, input: &Input<'_>) -> Result<(), DecodeError> {
if self.ty == Type::String {
let (bytes, lens) = self.strings(input.lens.len(), input.bytes.len())?;
bytes.extend_from_slice(input.bytes);
lens.extend_from_slice(input.lens);
} else {
self.fixed(input.width, input.len())?
.extend_from_slice(input.bytes);
}
Ok(())
}
pub(crate) fn validate(&self) -> Result<(), DecodeError> {
if !self.ready {
return Err(DecodeError::invalid(InvalidDataKind::Graph));
}
let lens = self.lens.len().saturating_mul(4);
if self.width == 0
|| !self.bytes.len().is_multiple_of(self.width)
|| self.bytes.len().saturating_add(lens) > self.budget
{
return Err(corrupt());
}
if self.ty == Type::String {
let sum = self.lens.iter().map(|&len| len as u64).sum::<u64>();
if sum != self.bytes.len() as u64 {
return Err(corrupt());
}
} else if !self.lens.is_empty() {
return Err(corrupt());
}
Ok(())
}
pub(crate) fn as_input(&self) -> Input<'_> {
Input {
ty: self.ty,
width: self.width.max(1),
bytes: &self.bytes,
lens: &self.lens,
}
}
pub(crate) fn bytes(&self) -> &[u8] {
&self.bytes
}
pub(crate) fn retained_bytes(&self) -> usize {
self.bytes.capacity() + self.lens.capacity() * 4
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_streams_respect_width_type_and_budget() {
let mut stream = Stream::default();
stream.prepare(Type::Serial, 16);
assert_eq!(stream.fixed(2, 1).map(|_| ()), Err(corrupt()));
stream.prepare(Type::Numeric, 16);
assert_eq!(
stream.validate(),
Err(DecodeError::invalid(InvalidDataKind::Graph))
);
assert_eq!(stream.fixed(0, 1).map(|_| ()), Err(corrupt()));
assert_eq!(stream.fixed(3, 1).map(|_| ()), Err(corrupt()));
assert_eq!(stream.fixed(4, 5).map(|_| ()), Err(corrupt()));
assert_eq!(stream.fixed(usize::MAX, 2).map(|_| ()), Err(corrupt()));
stream
.fixed(4, 2)
.unwrap()
.extend_from_slice(&[1, 0, 0, 0, 2, 0, 0, 0]);
assert_eq!(stream.validate(), Ok(()));
assert_eq!(stream.ty(), Type::Numeric);
let input = stream.as_input();
assert_eq!((input.len(), input.width), (2, 4));
assert_eq!(stream.bytes().len(), 8);
assert!(stream.retained_bytes() >= 8);
stream.fixed(4, 1).unwrap().extend_from_slice(&[1, 2, 3]);
assert_eq!(stream.validate(), Err(corrupt()));
stream.fixed(1, 1).unwrap().extend_from_slice(&[0; 17]);
assert_eq!(stream.validate(), Err(corrupt()));
assert_eq!(stream.strings(0, 0).map(|_| ()), Err(corrupt()));
}
#[test]
fn string_streams_check_their_lengths() {
let mut stream = Stream::default();
stream.prepare(Type::String, 16);
assert_eq!(stream.fixed(1, 1).map(|_| ()), Err(corrupt()));
assert_eq!(stream.strings(3, 5).map(|_| ()), Err(corrupt()));
assert_eq!(stream.strings(usize::MAX, 0).map(|_| ()), Err(corrupt()));
let (bytes, lens) = stream.strings(2, 3).unwrap();
bytes.extend_from_slice(b"abc");
lens.extend_from_slice(&[1, 2]);
assert_eq!(stream.validate(), Ok(()));
assert_eq!(stream.as_input().len(), 2);
let (_, lens) = stream.strings(1, 0).unwrap();
lens.push(1);
assert_eq!(stream.validate(), Err(corrupt()));
}
#[test]
fn copies_keep_the_target_type() {
let source = [7u8, 8, 9, 10];
let mut stream = Stream::default();
stream.prepare(Type::Struct, 64);
let mut input = Input::serial(&source);
input.width = 2;
stream.copy_of(&input).unwrap();
assert_eq!(stream.validate(), Ok(()));
assert_eq!(
(stream.as_input().ty, stream.as_input().len()),
(Type::Struct, 2)
);
let lens = [1u32, 3];
let strings = Input {
ty: Type::String,
width: 1,
bytes: &source,
lens: &lens,
};
stream.prepare(Type::String, 64);
stream.copy_of(&strings).unwrap();
assert_eq!(stream.as_input().lens, lens);
stream.prepare(Type::Serial, 64);
stream.fixed(1, 0).unwrap();
stream.lens.push(0);
assert_eq!(stream.validate(), Err(corrupt()));
assert_eq!(Input::serial(&[]).len(), 0);
}
}