use alloc::vec::Vec;
use fearless_simd::Level;
use super::Params;
use super::chunk::{CodecRecord, push_header_with};
use super::data::{Stream, Type, type_code};
use super::error::ExecError;
use super::rtgraph::RtGraph;
use super::sink::{Full, Sink};
use crate::dict::BundleId;
use crate::shared::wire::{
BUNDLE_VERSION_MIN, COMMENT_VERSION_MIN, FLAG_BUNDLE_ID, FLAG_COMMENT,
FLAG_COMPRESSED_CHECKSUM, FLAG_CONTENT_CHECKSUM, MAGIC_BASE,
};
use crate::shared::{varint, xxh3};
pub(crate) const COMMENT_MAX: usize = 10_000;
const fn varint_size(value: u64) -> usize {
((64 - (value | 1).leading_zeros()) as usize).div_ceil(7)
}
#[derive(Debug)]
pub(crate) struct Plan {
pub(crate) codecs: Vec<CodecRecord>,
pub(crate) distances: Vec<u32>,
pub(crate) dict: Vec<Option<u32>>,
pub(crate) headers: Vec<u8>,
pub(crate) stored: Vec<u32>,
pub(crate) n_inputs: usize,
}
pub(crate) fn final_graph(
rt: &RtGraph<'_>,
n_inputs: usize,
version: u32,
) -> Result<Plan, ExecError> {
let _ = version;
let n_nodes = rt.nodes.len();
if n_nodes >= 20_000 || rt.streams.len() + 1 >= 110_000 {
return Err(ExecError::Limit("nodes or streams"));
}
let mut plan = Plan {
codecs: Vec::with_capacity(n_nodes),
distances: Vec::new(),
dict: Vec::with_capacity(n_nodes),
headers: Vec::new(),
stored: Vec::new(),
n_inputs,
};
for rank in (0..n_nodes).rev() {
let node = &rt.nodes[rank];
if node.n_out < node.n_singletons {
return Err(ExecError::GraphInvalid);
}
let (start, len) = node.header;
plan.headers
.extend_from_slice(&rt.staging[start..start + len]);
plan.codecs.push(CodecRecord {
standard: true,
id: node.codec,
header_size: len as u32,
variable_outputs: node.n_out - node.n_singletons,
regenerated: node.inputs.len() as u32,
});
plan.dict.push(node.dict_index);
for &input in &node.inputs {
plan.distances.push(node.start_out - input - 1);
}
}
for index in (0..rt.streams.len()).rev() {
if rt.streams[index].to_store {
plan.stored.push(index as u32);
}
}
Ok(plan)
}
pub(crate) fn header_bound(plan: &Plan) -> usize {
let nodes = plan.codecs.len();
let buffers = plan.stored.len() + 1;
4 + plan.n_inputs * 21
+ varint_size(nodes as u64)
+ varint_size((buffers - 1) as u64)
+ buffers * 4
+ nodes * 22
+ plan.distances.len() * 4
+ 4
+ 4
+ nodes.div_ceil(8)
+ 1
+ nodes * 3
}
fn stored_bytes<'r>(rt: &'r RtGraph<'_>, index: u32) -> &'r [u8] {
rt.stream(index).map_or(&[][..], Stream::bytes)
}
pub(crate) fn write_chunk_header(
plan: &Plan,
version: u32,
lane: bool,
rt: &RtGraph<'_>,
out: &mut Vec<u8>,
) {
let sizes: Vec<usize> = plan
.stored
.iter()
.map(|&index| stored_bytes(rt, index).len())
.collect();
push_header_with(
out,
version,
&plan.codecs,
lane.then_some(&plan.dict[..]),
&plan.distances,
&sizes,
);
}
pub(crate) fn stored_size(plan: &Plan, rt: &RtGraph<'_>) -> usize {
plan.headers.len()
+ plan
.stored
.iter()
.map(|&index| stored_bytes(rt, index).len())
.sum::<usize>()
}
pub(crate) fn put_stored(plan: &Plan, rt: &RtGraph<'_>, sink: &mut dyn Sink) -> Result<(), Full> {
sink.put(&plan.headers)?;
for &index in &plan.stored {
sink.put(stored_bytes(rt, index))?;
}
Ok(())
}
pub(crate) fn content_checksum(inputs: &[Stream<'_>], version: u32, simd: Level) -> u32 {
let _ = version;
if let [only] = inputs
&& only.ty() != Type::String
{
return xxh3::hash32(simd, only.bytes());
}
let mut all = Vec::new();
for input in inputs {
all.extend_from_slice(input.bytes());
if input.ty() == Type::String {
all.extend(input.lens().iter().flat_map(|len| len.to_le_bytes()));
}
}
xxh3::hash32(simd, &all)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct InputDesc {
pub(crate) ty: Type,
pub(crate) size: usize,
pub(crate) n_elts: usize,
}
impl From<&Stream<'_>> for InputDesc {
fn from(stream: &Stream<'_>) -> Self {
Self {
ty: stream.ty(),
size: stream.content_size(),
n_elts: stream.n_elts(),
}
}
}
pub(crate) fn write_frame_header_for(
params: &Params,
simd: Level,
inputs: &[Stream<'_>],
comment: Option<&[u8]>,
bundle: Option<&BundleId>,
sink: &mut dyn Sink,
) -> Result<(), ExecError> {
let descs: Vec<InputDesc> = inputs.iter().map(InputDesc::from).collect();
write_frame_header(params, simd, &descs, comment, bundle, sink)
}
pub(crate) fn write_frame_header(
params: &Params,
simd: Level,
inputs: &[InputDesc],
comment: Option<&[u8]>,
bundle: Option<&BundleId>,
sink: &mut dyn Sink,
) -> Result<(), ExecError> {
let bundle = bundle.filter(|_| params.version >= BUNDLE_VERSION_MIN);
let comment =
comment.filter(|comment| !comment.is_empty() && params.version >= COMMENT_VERSION_MIN);
let comment_len = comment.map_or(0, <[u8]>::len);
if comment_len > COMMENT_MAX {
return Err(ExecError::Parameter("header comment too long"));
}
let bound = 4
+ inputs.len() * 21
+ 1
+ varint_size(u64::MAX)
+ 3
+ 8
+ if comment_len > 0 { 4 + comment_len } else { 0 }
+ if bundle.is_some() { 1 + 32 } else { 0 };
if sink.remaining().is_some_and(|room| room < bound) {
return Err(ExecError::Full);
}
let mut out = Vec::with_capacity(bound);
out.extend_from_slice(&(MAGIC_BASE + params.version).to_le_bytes());
let mut flags = 0;
if params.content_checksum {
flags |= FLAG_CONTENT_CHECKSUM;
}
if params.compressed_checksum {
flags |= FLAG_COMPRESSED_CHECKSUM;
}
if comment_len > 0 {
flags |= FLAG_COMMENT;
}
if bundle.is_some() {
flags |= FLAG_BUNDLE_ID;
}
out.push(flags);
if let Some(bundle) = bundle {
let bytes: &[u8] = bundle.as_ref();
let significant = bytes
.iter()
.rposition(|&byte| byte != 0)
.map_or(0, |at| at + 1);
if significant == 0 {
return Err(ExecError::Parameter("invalid (zero) bundle ID"));
}
out.push(significant as u8);
out.extend_from_slice(&bytes[..significant]);
}
let n = inputs.len();
let code = |index: usize| u64::from(type_code(inputs[index].ty));
if n < 15 {
let mut token = n as u8;
for index in 0..n.min(2) {
token |= (code(index) as u8) << (2 * index + 4);
}
out.push(token);
let mut types = 0u64;
for index in 2..n {
types |= code(index) << (2 * (index - 2));
}
let bytes = n.saturating_sub(2).div_ceil(4);
out.extend_from_slice(&types.to_le_bytes()[..bytes]);
} else {
out.push((((n - 15) << 4) | 15) as u8);
out.push(((n - 15) >> 4) as u8);
let mut index = 0;
let mut packed = Vec::with_capacity(n.div_ceil(4));
while index < n {
let limit = (index + 32).min(n);
let mut types = 0u64;
for position in index..limit {
types |= code(position) << ((2 * position) % 64);
}
packed.extend_from_slice(&types.to_le_bytes());
index = limit;
}
packed.truncate(n.div_ceil(4));
out.extend_from_slice(&packed);
}
for input in inputs {
varint::push(&mut out, input.size as u64 + 1);
}
for input in inputs {
if input.ty == Type::String {
varint::push(&mut out, input.n_elts as u64);
}
}
if let Some(comment) = comment {
varint::push(&mut out, comment_len as u64);
out.extend_from_slice(comment);
}
if params.compressed_checksum {
out.push(xxh3::hash64(simd, &out) as u8);
}
sink.put(&out)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Backend;
use crate::compressor::core::sink::VecSink;
fn params(version: u32) -> Params {
Params {
version,
..Params::from(&crate::EncoderConfig::DEFAULT)
}
}
fn header(version: u32, bundle: Option<&BundleId>) -> Result<Vec<u8>, ExecError> {
let mut out = Vec::new();
let input = InputDesc {
ty: Type::Serial,
size: 300,
n_elts: 0,
};
write_frame_header(
¶ms(version),
Backend::default().0,
&[input],
None,
bundle,
&mut VecSink::new(&mut out),
)?;
Ok(out)
}
#[test]
fn the_bundle_id_follows_the_flags_without_trailing_zeros() {
let mut id = [0u8; 32];
id[..3].copy_from_slice(&[9, 8, 7]);
let bundle = BundleId::from(id);
let out = header(27, Some(&bundle)).unwrap();
assert_eq!(out[4] & 8, 8);
assert_eq!(&out[5..9], &[3, 9, 8, 7]);
let plain = header(27, None).unwrap();
assert_eq!(plain[4] & 8, 0);
assert_eq!(out.len(), plain.len() + 4);
assert_eq!(
header(24, Some(&bundle)).unwrap(),
header(24, None).unwrap()
);
}
#[test]
fn an_all_zero_bundle_id_is_refused() {
let zero = BundleId::from([0; 32]);
assert!(matches!(
header(27, Some(&zero)),
Err(ExecError::Parameter(_))
));
}
}