use core::marker::PhantomData;
use heapless::Vec;
use tamp_sys::{
TampCompressor, TampConf, tamp_compressor_compress_cb, tamp_compressor_flush,
tamp_compressor_full, tamp_compressor_init, tamp_compressor_poll, tamp_compressor_sink,
tamp_initialize_dictionary,
};
use crate::Error;
#[derive(Clone)]
pub struct Config {
pub window_bits: u8,
pub literal_bits: u8,
pub lazy_matching: bool,
pub use_custom_dictionary: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
window_bits: 10, literal_bits: 8,
lazy_matching: false,
use_custom_dictionary: false,
}
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn window_bits(mut self, bits: u8) -> Result<Self, Error> {
if !(8..=15).contains(&bits) {
return Err(Error::InvalidConfig("Window bits must be 8-15"));
}
self.window_bits = bits;
Ok(self)
}
pub fn literal_bits(mut self, bits: u8) -> Result<Self, Error> {
if !(5..=8).contains(&bits) {
return Err(Error::InvalidConfig("Literal bits must be 5-8"));
}
self.literal_bits = bits;
Ok(self)
}
pub fn lazy_matching(mut self, enabled: bool) -> Self {
self.lazy_matching = enabled;
self
}
pub fn custom_dictionary(mut self, enabled: bool) -> Self {
self.use_custom_dictionary = enabled;
self
}
pub(crate) fn to_c_config(&self) -> TampConf {
let mut conf = TampConf {
_bitfield_align_1: [],
_bitfield_1: Default::default(),
};
conf.set_window(self.window_bits as u16);
conf.set_literal(self.literal_bits as u16);
conf.set_use_custom_dictionary(self.use_custom_dictionary as u16);
conf
}
pub fn window_size(&self) -> usize {
1usize << self.window_bits
}
}
pub struct Compressor<const N: usize> {
inner: TampCompressor,
window: Vec<u8, N>,
_marker: PhantomData<*mut ()>, }
impl<const N: usize> Compressor<N> {
pub fn new(config: Config) -> Result<Self, Error> {
Self::with_dictionary(config, None)
}
pub fn with_dictionary(config: Config, dictionary: Option<&[u8]>) -> Result<Self, Error> {
let expected_size = config.window_size();
if N != expected_size {
return Err(Error::InvalidConfig(
"Buffer size N must equal 2^window_bits",
));
}
let mut window = Vec::new();
window.resize(N, 0).map_err(|_| Error::BufferTooSmall)?;
if let Some(dict) = dictionary {
if config.use_custom_dictionary {
let copy_len = dict.len().min(N);
window[..copy_len].copy_from_slice(&dict[..copy_len]);
} else {
unsafe {
tamp_initialize_dictionary(window.as_mut_ptr(), N);
}
if !dict.is_empty() {
let copy_len = dict.len().min(N);
window[..copy_len].copy_from_slice(&dict[..copy_len]);
}
}
} else if config.use_custom_dictionary {
return Err(Error::InvalidConfig(
"Custom dictionary enabled but none provided",
));
}
let mut compressor = Self {
inner: unsafe { core::mem::zeroed() },
window,
_marker: PhantomData,
};
let c_config = config.to_c_config();
let result = unsafe {
tamp_compressor_init(
&mut compressor.inner,
&c_config,
compressor.window.as_mut_ptr(),
)
};
Error::from_tamp_res(result)?;
Ok(compressor)
}
pub fn compress_chunk(
&mut self,
input: &[u8],
output: &mut [u8],
) -> Result<(usize, usize), Error> {
let mut input_consumed = 0;
let mut output_written = 0;
let result = unsafe {
tamp_compressor_compress_cb(
&mut self.inner,
output.as_mut_ptr(),
output.len(),
&mut output_written,
input.as_ptr(),
input.len(),
&mut input_consumed,
None, core::ptr::null_mut(), )
};
Error::from_tamp_res(result)?;
Ok((input_consumed, output_written))
}
pub fn sink(&mut self, input: &[u8]) -> usize {
let mut consumed = 0;
unsafe {
tamp_compressor_sink(&mut self.inner, input.as_ptr(), input.len(), &mut consumed);
}
consumed
}
pub fn poll(&mut self, output: &mut [u8]) -> Result<usize, Error> {
let mut output_written = 0;
let result = unsafe {
tamp_compressor_poll(
&mut self.inner,
output.as_mut_ptr(),
output.len(),
&mut output_written,
)
};
Error::from_tamp_res(result)?;
Ok(output_written)
}
pub fn is_full(&self) -> bool {
unsafe { tamp_compressor_full(&self.inner as *const _ as *mut _) }
}
pub fn flush(&mut self, output: &mut [u8], write_token: bool) -> Result<usize, Error> {
let mut output_written = 0;
let result = unsafe {
tamp_compressor_flush(
&mut self.inner,
output.as_mut_ptr(),
output.len(),
&mut output_written,
write_token,
)
};
Error::from_tamp_res(result)?;
Ok(output_written)
}
}