202 lines
6.5 KiB
Rust
202 lines
6.5 KiB
Rust
//! # Compression routines
|
|
//!
|
|
//! Available with feature `compress`.
|
|
//!
|
|
//! [`compress`] and [`compress_with_dict`] available with features `std` and/or `alloc`.
|
|
//!
|
|
//! # Examples
|
|
//!
|
|
//! Compressing a buffer into a heap-allocated vector:
|
|
//! ```
|
|
//! use lzokay::compress::*;
|
|
//! # #[allow(non_upper_case_globals)] const input: [u8; 512] = [0u8; 512];
|
|
//!
|
|
//! let dst: Vec<u8> = compress(&input)?;
|
|
//! # assert_eq!(dst.len(), 10);
|
|
//! # Ok::<(), lzokay::Error>(())
|
|
//! ```
|
|
//!
|
|
//! Several compression calls with shared dictionary, avoiding needless work:
|
|
//! ```
|
|
//! use lzokay::compress::*;
|
|
//! # #[allow(non_upper_case_globals)] const input1: [u8; 512] = [0u8; 512];
|
|
//! # #[allow(non_upper_case_globals)] const input2: [u8; 512] = [0u8; 512];
|
|
//!
|
|
//! let mut dict = new_dict();
|
|
//! let dst1 = compress_with_dict(&input1, &mut dict)?;
|
|
//! let dst2 = compress_with_dict(&input2, &mut dict)?;
|
|
//! # assert_eq!(dst1.len(), 10);
|
|
//! # assert_eq!(dst2.len(), 10);
|
|
//! # Ok::<(), lzokay::Error>(())
|
|
//! ```
|
|
//!
|
|
//! `#![no_std]` compatible compression:
|
|
//! ```
|
|
//! use lzokay::compress::*;
|
|
//! # #[allow(non_upper_case_globals)] const input: [u8; 512] = [0u8; 512];
|
|
//!
|
|
//! // Allocate dst on stack, with worst-case compression size
|
|
//! let mut dst = [0u8; compress_worst_size(input.len())];
|
|
//! // Allocate dictionary storage on stack
|
|
//! let mut storage = [0u8; dict_storage_size()];
|
|
//! // Create dictionary from storage
|
|
//! let mut dict = dict_from_storage(&mut storage);
|
|
//! let size = compress_no_alloc(&input, &mut dst, &mut dict)?;
|
|
//! # assert_eq!(size, 10);
|
|
//! # Ok::<(), lzokay::Error>(())
|
|
//! ```
|
|
|
|
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
|
extern crate alloc;
|
|
|
|
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
|
use alloc::{
|
|
alloc::{alloc_zeroed, dealloc, Layout},
|
|
boxed::Box,
|
|
vec::Vec,
|
|
};
|
|
use core::{marker::PhantomData, mem::size_of, ptr::null_mut};
|
|
|
|
use crate::{bindings, lzokay_result, Error};
|
|
|
|
type DictStorage = bindings::lzokay_DictBase_storage_type;
|
|
|
|
/// Dictionary type
|
|
pub struct Dict<'a> {
|
|
base: bindings::lzokay_DictBase,
|
|
#[cfg(feature = "alloc")]
|
|
storage: Option<Box<[u8; dict_storage_size()]>>,
|
|
phantom: PhantomData<&'a DictStorage>,
|
|
}
|
|
|
|
/// Creates a new heap-allocated dictionary.
|
|
#[cfg(feature = "alloc")]
|
|
pub fn new_dict() -> Dict<'static> {
|
|
let mut dict = Dict {
|
|
base: bindings::lzokay_DictBase { _storage: null_mut() },
|
|
storage: Option::Some(Box::new([0u8; dict_storage_size()])),
|
|
phantom: PhantomData,
|
|
};
|
|
dict.base._storage = dict.storage.as_mut().unwrap().as_mut_ptr() as *mut DictStorage;
|
|
dict
|
|
}
|
|
|
|
/// Dictionary storage size, for manual or stack allocation.
|
|
pub const fn dict_storage_size() -> usize { size_of::<DictStorage>() }
|
|
|
|
/// Creates a dictionary from the supplied storage.
|
|
///
|
|
/// Storage **must** be at least [`dict_storage_size()`] bytes,
|
|
/// otherwise this function will panic.
|
|
pub fn dict_from_storage(storage: &mut [u8]) -> Dict {
|
|
if storage.len() < dict_storage_size() {
|
|
panic!(
|
|
"Dictionary storage is not large enough: {}, expected {}",
|
|
storage.len(),
|
|
dict_storage_size()
|
|
);
|
|
}
|
|
Dict {
|
|
base: bindings::lzokay_DictBase { _storage: storage.as_mut_ptr() as *mut DictStorage },
|
|
storage: Option::None,
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Worst-case compression size.
|
|
pub const fn compress_worst_size(s: usize) -> usize { s + s / 16 + 64 + 3 }
|
|
|
|
/// Compress the supplied buffer into a heap-allocated vector.
|
|
///
|
|
/// Creates a new dictionary for each invocation.
|
|
#[cfg(feature = "alloc")]
|
|
pub fn compress(src: &[u8]) -> Result<Vec<u8>, Error> { compress_with_dict(src, &mut new_dict()) }
|
|
|
|
/// Compress the supplied buffer into a heap-allocated vector,
|
|
/// with the supplied pre-allocated dictionary.
|
|
#[cfg(feature = "alloc")]
|
|
pub fn compress_with_dict(src: &[u8], dict: &mut Dict) -> Result<Vec<u8>, Error> {
|
|
let mut out_size = 0usize;
|
|
let capacity = compress_worst_size(src.len());
|
|
let mut dst = Vec::with_capacity(capacity);
|
|
let result = unsafe {
|
|
let result = bindings::lzokay_compress(
|
|
src.as_ptr(),
|
|
src.len(),
|
|
dst.as_mut_ptr(),
|
|
capacity,
|
|
&mut out_size,
|
|
&mut dict.base,
|
|
);
|
|
if result == bindings::lzokay_EResult_Success {
|
|
dst.set_len(out_size as usize);
|
|
}
|
|
result
|
|
};
|
|
lzokay_result(dst, result)
|
|
}
|
|
|
|
/// Compress the supplied buffer.
|
|
///
|
|
/// For sizing `dst`, use [`compress_worst_size`].
|
|
pub fn compress_no_alloc(src: &[u8], dst: &mut [u8], dict: &mut Dict) -> Result<usize, Error> {
|
|
let mut out_size = 0usize;
|
|
let result = unsafe {
|
|
bindings::lzokay_compress(
|
|
src.as_ptr(),
|
|
src.len(),
|
|
dst.as_mut_ptr(),
|
|
dst.len(),
|
|
&mut out_size,
|
|
&mut dict.base,
|
|
)
|
|
};
|
|
lzokay_result(out_size as usize, result)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[cfg(feature = "alloc")]
|
|
use crate::compress::{compress, compress_with_dict, new_dict};
|
|
use crate::compress::{
|
|
compress_no_alloc, compress_worst_size, dict_from_storage, dict_storage_size,
|
|
};
|
|
|
|
const INPUT_1: &[u8] = include_bytes!("test1.txt");
|
|
const EXPECTED_1: &[u8] = include_bytes!("test1.bin");
|
|
const INPUT_2: &[u8] = include_bytes!("test2.txt");
|
|
const EXPECTED_2: &[u8] = include_bytes!("test2.bin");
|
|
|
|
#[test]
|
|
#[cfg(feature = "alloc")]
|
|
fn test_compress() {
|
|
let dst = compress(INPUT_1).expect("Failed to compress");
|
|
assert_eq!(dst, EXPECTED_1);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "alloc")]
|
|
fn test_compress_with_dict() {
|
|
let mut dict = new_dict();
|
|
let dst = compress_with_dict(INPUT_1, &mut dict).expect("Failed to compress (1)");
|
|
assert_eq!(dst, EXPECTED_1);
|
|
// Compress a second time to test dictionary reuse
|
|
let dst = compress_with_dict(INPUT_2, &mut dict).expect("Failed to compress (2)");
|
|
assert_eq!(dst, EXPECTED_2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_compress_no_alloc() {
|
|
let mut dst = [0u8; compress_worst_size(INPUT_1.len())];
|
|
let mut storage = [0u8; dict_storage_size()];
|
|
let mut dict = dict_from_storage(&mut storage);
|
|
let out_size =
|
|
compress_no_alloc(INPUT_1, &mut dst, &mut dict).expect("Failed to compress (1)");
|
|
assert_eq!(&dst[0..out_size], EXPECTED_1);
|
|
// Compress a second time to test dictionary reuse
|
|
let out_size =
|
|
compress_no_alloc(INPUT_2, &mut dst, &mut dict).expect("Failed to compress (2)");
|
|
assert_eq!(&dst[0..out_size], EXPECTED_2);
|
|
}
|
|
}
|