pub(crate) mod chunk;
pub(crate) mod frame;
pub(crate) mod sink;
pub(crate) mod stream;
#[cfg(feature = "zstd")]
pub(crate) mod zstd;
use alloc::vec::Vec;
use fearless_simd::Level;
use super::config::EncoderConfig;
use crate::{Backend, RetentionPolicy};
use sink::{Full, Sink};
#[cfg(feature = "zstd")]
use zstd::ZstdNode;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Params {
pub(crate) version: u32,
pub(crate) level: i32,
pub(crate) content_checksum: bool,
pub(crate) compressed_checksum: bool,
pub(crate) chunk_limit: usize,
}
impl From<&EncoderConfig> for Params {
fn from(config: &EncoderConfig) -> Self {
Self {
version: config.format_version().get(),
level: config.level().get(),
content_checksum: config.content_checksum(),
compressed_checksum: config.compressed_checksum(),
chunk_limit: config.chunking().limit(),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum CoreError {
OutputFull,
#[cfg(feature = "zstd")]
Internal,
}
impl From<Full> for CoreError {
fn from(_: Full) -> Self {
Self::OutputFull
}
}
#[derive(Debug)]
pub(crate) struct Engine {
#[cfg(feature = "zstd")]
zstd: ZstdNode,
header: Vec<u8>,
simd: Level,
}
impl Engine {
pub(crate) fn new(params: &Params, backend: Backend) -> Self {
#[cfg(not(feature = "zstd"))]
let _ = params;
Self {
#[cfg(feature = "zstd")]
zstd: ZstdNode::new(params.level, backend),
header: Vec::new(),
simd: backend.0,
}
}
pub(crate) fn reconfigure(&mut self, params: &Params) {
#[cfg(feature = "zstd")]
self.zstd.set_level(params.level);
#[cfg(not(feature = "zstd"))]
let _ = params;
}
pub(crate) fn header(
&self,
params: &Params,
total: u64,
sink: &mut impl Sink,
) -> Result<(), CoreError> {
Ok(frame::write_header(params, self.simd, total, sink)?)
}
pub(crate) fn chunk(
&mut self,
params: &Params,
input: &[u8],
sink: &mut impl Sink,
) -> Result<(), CoreError> {
chunk::write_chunk(self, params, input, sink)
}
pub(crate) fn frame(
&mut self,
params: &Params,
input: &[u8],
sink: &mut impl Sink,
) -> Result<(), CoreError> {
self.header(params, input.len() as u64, sink)?;
if input.is_empty() {
self.chunk(params, input, sink)?;
}
for piece in input.chunks(params.chunk_limit) {
self.chunk(params, piece, sink)?;
}
Ok(frame::write_end(sink)?)
}
pub(crate) fn retained_bytes(&self) -> usize {
#[cfg(feature = "zstd")]
let codecs = self.zstd.retained_bytes();
#[cfg(not(feature = "zstd"))]
let codecs = 0;
codecs + self.header.capacity()
}
pub(crate) fn trim(&mut self, policy: RetentionPolicy) {
#[cfg(feature = "zstd")]
self.zstd.trim(policy);
if !policy.allows(self.retained_bytes()) {
self.header = Vec::new();
}
}
}
#[cfg(test)]
mod tests {
use super::sink::{SliceSink, VecSink};
use super::*;
#[cfg(feature = "zstd")]
#[test]
fn whole_frames_split_at_the_chunk_limit() {
let mut params = Params::from(&EncoderConfig::default());
params.chunk_limit = 32 << 10;
let input = alloc::vec![3u8; (64 << 10) + 1];
let mut engine = Engine::new(¶ms, Backend::default());
let mut out = Vec::new();
engine
.frame(¶ms, &input, &mut VecSink::new(&mut out))
.unwrap();
let expected = openzl_ffi::compress(
&input,
openzl_ffi::Params {
chunk_size: 32 << 10,
..Default::default()
},
)
.unwrap();
assert_eq!(out, expected);
}
#[test]
fn a_full_slice_reports_output_full() {
let params = Params::from(&EncoderConfig::default());
let mut engine = Engine::new(¶ms, Backend::default());
let mut buffer = [0u8; 12];
let error = engine.frame(
¶ms,
b"0123456789abcdef",
&mut SliceSink::new(&mut buffer),
);
assert_eq!(error, Err(CoreError::OutputFull));
assert_eq!(CoreError::from(Full), CoreError::OutputFull);
}
#[test]
fn trimming_releases_scratch() {
let params = Params::from(&EncoderConfig::default());
let mut engine = Engine::new(¶ms, Backend::default());
let mut out = Vec::new();
engine
.frame(¶ms, &[1u8; 5000], &mut VecSink::new(&mut out))
.unwrap();
assert!(engine.retained_bytes() > 0);
engine.trim(RetentionPolicy::ReleaseAll);
assert_eq!(engine.header.capacity(), 0);
}
}