use mzstd::{Compressor, Decompressor, EncoderConfig, Level};
use super::{Error, FrameError, Result};
pub use crate::s0_const::MAX_BLOCK;
pub const HEADER: usize = 4;
pub const SLACK: usize = 4096;
pub const FALLBACK_PCT: usize = 97;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Encoded {
pub len: usize,
pub compressed: bool,
}
impl Encoded {
pub const fn ratio_pct(&self, raw_len: usize) -> usize {
if raw_len == 0 {
return 0;
}
self.len * 100 / raw_len
}
}
pub struct Codec {
c: Compressor,
d: Decompressor,
level: i32,
}
impl Codec {
pub fn new(level: i32) -> Result<Self> {
Ok(Self {
c: Compressor::new(config(level)?)?,
d: Decompressor::default(),
level,
})
}
pub const fn level(&self) -> i32 {
self.level
}
pub fn set_level(&mut self, level: i32) -> Result<()> {
if level != self.level {
self.c.reconfigure(config(level)?)?;
self.level = level;
}
Ok(())
}
pub fn try_compress_block(&mut self, raw: &[u8], dst: &mut [u8]) -> Result<Option<usize>> {
if dst.len() < raw.len().saturating_add(SLACK) {
return Err(Error::Overflow);
}
if raw.is_empty() {
return Ok(None);
}
let n = self.c.compress_to_slice(raw, &mut dst[HEADER..])?;
let total = n + HEADER;
if total * 100 >= raw.len() * FALLBACK_PCT {
return Ok(None);
}
dst[..HEADER].copy_from_slice(&(n as u32).to_le_bytes());
Ok(Some(total))
}
pub fn compress_block(&mut self, raw: &[u8], dst: &mut [u8]) -> Result<Encoded> {
if let Some(len) = self.try_compress_block(raw, dst)? {
Ok(Encoded {
len,
compressed: true,
})
} else {
dst[..raw.len()].copy_from_slice(raw);
Ok(Encoded {
len: raw.len(),
compressed: false,
})
}
}
pub fn decompress_block(&mut self, src: &[u8], dst: &mut [u8]) -> Result<usize> {
let (head, body) = src
.split_first_chunk::<HEADER>()
.ok_or(Error::Frame(FrameError::ShortPrefix))?;
let frame = body
.get(..u32::from_le_bytes(*head) as usize)
.ok_or(Error::Frame(FrameError::LenOverflow))?;
Ok(self.d.decompress_to_slice(frame, dst)?)
}
}
fn config(level: i32) -> Result<EncoderConfig> {
let level = Level::try_from(level)?;
Ok(EncoderConfig::default().with_level(level))
}