lzzzz-2.0.0/.cargo_vcs_info.json0000644000000001360000000000100122360ustar { "git": { "sha1": "469f2a7ead6ac977940e34052966d87bc3949282" }, "path_in_vcs": "" }lzzzz-2.0.0/Cargo.toml0000644000000033420000000000100102360ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "lzzzz" version = "2.0.0" authors = ["picoHz "] build = "build.rs" include = [ "src/**/*", "build.rs", "vendor/liblz4/*", "Cargo.toml", "LICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Full-featured liblz4 binding for Rust." homepage = "https://github.com/picoHz/lzzzz" documentation = "https://docs.rs/lzzzz" readme = "README.md" keywords = [ "lz4", "lz4f", "lz4-hc", "compression", "decompression", ] categories = [ "compression", "api-bindings", ] license = "MIT" repository = "https://github.com/picoHz/lzzzz" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "lzzzz" path = "src/lib.rs" [dev-dependencies.assert_fs] version = "1.0.6" [dev-dependencies.base64] version = "0.22.1" [dev-dependencies.bytes] version = "1.1.0" [dev-dependencies.criterion] version = "0.5.1" [dev-dependencies.lazy_static] version = "1.4.0" [dev-dependencies.rand] version = "0.8.4" features = ["small_rng"] [dev-dependencies.rayon] version = "1.5.1" [dev-dependencies.static_assertions] version = "1.1.0" [build-dependencies.cc] version = "1.0.72" features = ["parallel"] lzzzz-2.0.0/Cargo.toml.orig0000644000000017000000000000100111710ustar [package] name = "lzzzz" version = "2.0.0" authors = ["picoHz "] edition = "2021" description = "Full-featured liblz4 binding for Rust." keywords = ["lz4", "lz4f", "lz4-hc", "compression", "decompression"] categories = ["compression", "api-bindings"] repository = "https://github.com/picoHz/lzzzz" homepage = "https://github.com/picoHz/lzzzz" documentation = "https://docs.rs/lzzzz" license = "MIT" readme = "README.md" include = ["src/**/*", "build.rs", "vendor/liblz4/*", "Cargo.toml", "LICENSE"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dev-dependencies] assert_fs = "1.0.6" base64 = "0.22.1" bytes = "1.1.0" lazy_static = "1.4.0" rand = { version = "0.8.4", features = ["small_rng"] } rayon = "1.5.1" static_assertions = "1.1.0" criterion = "0.5.1" [build-dependencies] cc = { version = "1.0.72", features = ["parallel"] } [[bench]] name = "lzzzz" harness = false path = "benches/lzzzz.rs" lzzzz-2.0.0/Cargo.toml.orig000064400000000000000000000017001046102023000137130ustar 00000000000000[package] name = "lzzzz" version = "2.0.0" authors = ["picoHz "] edition = "2021" description = "Full-featured liblz4 binding for Rust." keywords = ["lz4", "lz4f", "lz4-hc", "compression", "decompression"] categories = ["compression", "api-bindings"] repository = "https://github.com/picoHz/lzzzz" homepage = "https://github.com/picoHz/lzzzz" documentation = "https://docs.rs/lzzzz" license = "MIT" readme = "README.md" include = ["src/**/*", "build.rs", "vendor/liblz4/*", "Cargo.toml", "LICENSE"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dev-dependencies] assert_fs = "1.0.6" base64 = "0.22.1" bytes = "1.1.0" lazy_static = "1.4.0" rand = { version = "0.8.4", features = ["small_rng"] } rayon = "1.5.1" static_assertions = "1.1.0" criterion = "0.5.1" [build-dependencies] cc = { version = "1.0.72", features = ["parallel"] } [[bench]] name = "lzzzz" harness = false path = "benches/lzzzz.rs" lzzzz-2.0.0/LICENSE000064400000000000000000000020471046102023000120360ustar 00000000000000MIT License Copyright (c) 2020 picoHz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lzzzz-2.0.0/README.md000064400000000000000000000055071046102023000123140ustar 00000000000000
lzzzz Full-featured [liblz4](https://github.com/lz4/lz4) binding for Rust [![Crates.io](https://img.shields.io/crates/v/lzzzz.svg)](https://crates.io/crates/lzzzz) [![GitHub license](https://img.shields.io/github/license/picoHz/lzzzz.svg)](https://github.com/picoHz/lzzzz/blob/master/LICENSE) [![Rustdoc](https://img.shields.io/badge/doc-rustdoc-green.svg)](https://docs.rs/lzzzz) ![Rust](https://github.com/picoHz/lzzzz/workflows/Rust/badge.svg)
--- ## About Rust APIs for the [LZ4](https://lz4.github.io/lz4/) compression algorithm. - Supports almost all liblz4 features - Zero dependencies except liblz4 - Tested on Windows / macOS / Linux ## Usage Add this to your `Cargo.toml`: ```toml [dependencies] lzzzz = "1.0.3" ``` [API Documentation](https://docs.rs/lzzzz) ## Features - LZ4 - Compression (Block / Streaming) - Decompression (Block / Streaming) - Partial Decompression - Custom Dictionary - LZ4_HC - Compression (Block / Streaming) - Partial Compression - Custom Dictionary - LZ4F - Compression - Decompression - Custom Dictionary - Streaming I/O (`Read` / `BufRead` / `Write`) ## Examples ### Block Mode ```rust use lzzzz::{lz4, lz4_hc, lz4f}; let data = b"The quick brown fox jumps over the lazy dog."; // LZ4 compression let mut comp = Vec::new(); lz4::compress_to_vec(data, &mut comp, lz4::ACC_LEVEL_DEFAULT)?; // LZ4_HC compression let mut comp = Vec::new(); lz4_hc::compress_to_vec(data, &mut comp, lz4_hc::CLEVEL_DEFAULT)?; // LZ4/LZ4_HC decompression let mut decomp = vec![0; data.len()]; lz4::decompress(&comp, &mut decomp)?; // LZ4F compression let prefs = lz4f::Preferences::default(); let mut comp = Vec::new(); lz4f::compress_to_vec(data, &mut comp, &prefs)?; // LZ4F decompression let mut decomp = Vec::new(); lz4f::decompress_to_vec(&comp, &mut decomp)?; ``` ### Streaming Mode ```rust use lzzzz::{lz4, lz4_hc}; let data = b"The quick brown fox jumps over the lazy dog."; // LZ4 compression let mut comp = lz4::Compressor::new()?; let mut buf = Vec::new(); comp.next_to_vec(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; // LZ4_HC compression let mut comp = lz4_hc::Compressor::new()?; let mut buf = Vec::new(); comp.next_to_vec(data, &mut buf)?; // LZ4/LZ4_HC decompression let mut decomp = lz4::Decompressor::new()?; let result = decomp.next(&data, data.len())?; ``` ```rust use lzzzz::lz4f::{WriteCompressor, ReadDecompressor, Preferences}; use std::{fs::File, io::prelude::*}; // LZ4F Write-based compression let mut f = File::create("foo.lz4")?; let mut w = WriteCompressor::new(&mut f, Preferences::default())?; w.write_all(b"Hello world!")?; // LZ4F Read-based decompression let mut f = File::open("foo.lz4")?; let mut r = ReadDecompressor::new(&mut f)?; let mut buf = Vec::new(); r.read_to_end(&mut buf)?; ``` lzzzz-2.0.0/build.rs000064400000000000000000000004161046102023000124740ustar 00000000000000fn main() -> Result<(), cc::Error> { let sources = &["lz4.c", "lz4hc.c", "lz4frame.c", "xxhash.c"][..]; let dir = std::path::Path::new("vendor/liblz4"); cc::Build::new() .files(sources.iter().map(|file| dir.join(file))) .try_compile("lz4") } lzzzz-2.0.0/src/common/api.rs000064400000000000000000000011041046102023000142200ustar 00000000000000#![allow(unsafe_code)] use super::binding; use std::ffi::CStr; /// Returns the version number of liblz4. /// /// # Example /// /// ``` /// assert_eq!(lzzzz::version_number(), 11000); // 1.9.4 /// ``` pub fn version_number() -> u32 { unsafe { binding::LZ4_versionNumber() as u32 } } /// Returns the version string of liblz4. /// /// # Example /// /// ``` /// assert_eq!(lzzzz::version_string(), "1.10.0"); /// ``` pub fn version_string() -> &'static str { unsafe { CStr::from_ptr(binding::LZ4_versionString()) .to_str() .unwrap() } } lzzzz-2.0.0/src/common/binding.rs000064400000000000000000000002431046102023000150640ustar 00000000000000use std::os::raw::{c_char, c_int}; #[link(name = "lz4")] extern "C" { pub fn LZ4_versionNumber() -> c_int; pub fn LZ4_versionString() -> *const c_char; } lzzzz-2.0.0/src/common/error.rs000064400000000000000000000033111046102023000146020ustar 00000000000000use std::{convert, error, fmt, io, result}; /// A list specifying general categories of LZ4 error. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ErrorKind { /// The state initialization failed for some reason. InitializationFailed, /// The compression failed for some reason. CompressionFailed, /// The decompression failed for some reason. DecompressionFailed, /// The frame header had an invalid value. FrameHeaderInvalid, /// The decompressor reached unexpected EOF. CompressedDataIncomplete, /// Dictionary data was not consistent during the streaming decompression. DictionaryChangedDuringDecompression, } impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> { ::fmt(self, f) } } /// The error type for LZ4 operations. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Error { kind: ErrorKind, } impl Error { pub(crate) const fn new(kind: ErrorKind) -> Self { Self { kind } } /// Returns the corresponding `ErrorKind` for this error. pub const fn kind(self) -> ErrorKind { self.kind } } impl convert::From for io::Error { fn from(err: Error) -> Self { Self::new(io::ErrorKind::Other, err) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> { ::fmt(&self.kind, f) } } impl error::Error for Error {} /// A specialized [`Result`] type for LZ4 operations. /// /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html pub type Result = result::Result; lzzzz-2.0.0/src/common/mod.rs000064400000000000000000000003471046102023000142360ustar 00000000000000mod api; mod binding; mod error; pub use api::{version_number, version_string}; pub use error::{Error, ErrorKind, Result}; pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; pub(crate) const DICTIONARY_SIZE: usize = 64 * 1024; lzzzz-2.0.0/src/lib.rs000064400000000000000000000003141046102023000127270ustar 00000000000000//! Full-featured liblz4 binding for Rust. #![deny(unsafe_code)] #![deny(clippy::all)] #![cfg_attr(docsrs, feature(doc_cfg))] mod common; pub mod lz4; pub mod lz4_hc; pub mod lz4f; pub use common::*; lzzzz-2.0.0/src/lz4/binding.rs000064400000000000000000000063261046102023000143150ustar 00000000000000use std::{ mem, os::raw::{c_char, c_int, c_void}, }; const LZ4_MEMORY_USAGE: usize = 14; const LZ4_STREAMSIZE_U64: usize = (1 << (LZ4_MEMORY_USAGE - 3)) + 4; pub const LZ4_STREAMSIZE: usize = LZ4_STREAMSIZE_U64 * mem::size_of::(); #[repr(C)] pub struct LZ4Stream { _private: [u64; LZ4_STREAMSIZE_U64], } #[repr(C)] pub struct LZ4DecStream { _private: [u8; 0], } extern "C" { pub fn LZ4_compress_fast_extState( state: *mut c_void, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, acceleration: c_int, ) -> c_int; pub fn LZ4_compress_fast_extState_fastReset( state: *mut c_void, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, acceleration: c_int, ) -> c_int; pub fn LZ4_compress_destSize( src: *const c_char, dst: *mut c_char, src_size: *mut c_int, target_dst_size: c_int, ) -> c_int; pub fn LZ4_decompress_safe( src: *const c_char, dst: *mut c_char, compressed_size: c_int, dst_capacity: c_int, ) -> c_int; pub fn LZ4_decompress_safe_partial( src: *const c_char, dst: *mut c_char, src_size: c_int, target_output_size: c_int, dst_capacity: c_int, ) -> c_int; pub fn LZ4_decompress_safe_usingDict( src: *const c_char, dst: *mut c_char, compressed_size: c_int, dst_capacity: c_int, dict_start: *const c_char, dict_size: c_int, ) -> c_int; pub fn LZ4_decompress_safe_partial_usingDict( src: *const c_char, dst: *mut c_char, compressed_size: c_int, target_output_size: c_int, dst_capacity: c_int, dict_start: *const c_char, dict_size: c_int, ) -> c_int; pub fn LZ4_createStream() -> *mut LZ4Stream; pub fn LZ4_freeStream(ptr: *mut LZ4Stream) -> c_int; pub fn LZ4_initStream(buffer: *mut c_void, size: usize) -> *mut LZ4Stream; pub fn LZ4_loadDict(ptr: *mut LZ4Stream, dictionary: *const c_char, dict_size: c_int) -> c_int; pub fn LZ4_loadDictSlow(ptr: *mut LZ4Stream, dictionary: *const c_char, dict_size: c_int) -> c_int; pub fn LZ4_saveDict( ptr: *mut LZ4Stream, safe_buffer: *mut c_char, max_dict_size: c_int, ) -> c_int; pub fn LZ4_compress_fast_continue( ptr: *mut LZ4Stream, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, acceleration: c_int, ) -> c_int; pub fn LZ4_createStreamDecode() -> *mut LZ4DecStream; pub fn LZ4_freeStreamDecode(stream: *mut LZ4DecStream) -> c_int; pub fn LZ4_setStreamDecode( ptr: *mut LZ4DecStream, dictionary: *const c_char, dict_size: c_int, ) -> c_int; pub fn LZ4_decompress_safe_continue( ptr: *mut LZ4DecStream, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, ) -> c_int; pub fn LZ4_attach_dictionary( working_stream: *mut LZ4Stream, dictionary_stream: *const LZ4Stream, ); pub fn LZ4_resetStream_fast(streamPtr: *mut LZ4Stream); } lzzzz-2.0.0/src/lz4/block/api.rs000064400000000000000000000114151046102023000145410ustar 00000000000000#![allow(unsafe_code)] use super::super::binding; use crate::{Error, ErrorKind, Result}; use std::{ cell::RefCell, ops::Deref, os::raw::{c_char, c_int, c_void}, }; const LZ4_MAX_INPUT_SIZE: usize = 0x7E00_0000; pub const fn compress_bound(input_size: usize) -> usize { (input_size <= LZ4_MAX_INPUT_SIZE) as usize * (input_size + (input_size / 255) + 16) } pub const fn size_of_state() -> usize { binding::LZ4_STREAMSIZE } pub fn compress_fast_ext_state( state: &mut [u8], src: &[u8], dst: *mut u8, dst_len: usize, acceleration: i32, ) -> usize { unsafe { binding::LZ4_compress_fast_extState( state.as_mut_ptr() as *mut c_void, src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, acceleration as c_int, ) as usize } } pub fn compress_fast_ext_state_fast_reset( state: &mut [u8], src: &[u8], dst: *mut u8, dst_len: usize, acceleration: i32, ) -> usize { unsafe { binding::LZ4_compress_fast_extState_fastReset( state.as_mut_ptr() as *mut c_void, src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, acceleration as c_int, ) as usize } } pub fn compress_dest_size( src: &[u8], dst: &mut [u8], ) -> Result<(usize, usize)> { let mut src_size: c_int = src.len() as c_int; let result = unsafe { binding::LZ4_compress_destSize( src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, &mut src_size as *mut c_int, dst.len() as c_int, ) }; if result == 0 { Err(Error::new(ErrorKind::CompressionFailed)) } else { Ok((src_size as usize, result as usize)) } } pub fn decompress_safe(src: &[u8], dst: &mut [u8]) -> Result { let result = unsafe { binding::LZ4_decompress_safe( src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, src.len() as c_int, dst.len() as c_int, ) }; if result < 0 { Err(Error::new(ErrorKind::DecompressionFailed)) } else { Ok(result as usize) } } pub fn decompress_safe_partial(src: &[u8], dst: &mut [u8], original_size: usize) -> Result { let result = unsafe { binding::LZ4_decompress_safe_partial( src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, src.len() as c_int, original_size as c_int, dst.len() as c_int, ) }; if result < 0 { Err(Error::new(ErrorKind::DecompressionFailed)) } else { Ok(result as usize) } } pub fn decompress_safe_using_dict(src: &[u8], dst: &mut [u8], dict: &[u8]) -> Result { let result = unsafe { binding::LZ4_decompress_safe_usingDict( src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, src.len() as c_int, dst.len() as c_int, dict.as_ptr() as *const c_char, dict.len() as c_int, ) }; if result < 0 { Err(Error::new(ErrorKind::DecompressionFailed)) } else { Ok(result as usize) } } pub fn decompress_safe_partial_using_dict( src: &[u8], dst: &mut [u8], original_size: usize, dict: &[u8], ) -> Result { let result = unsafe { binding::LZ4_decompress_safe_partial_usingDict( src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, src.len() as c_int, original_size as c_int, dst.len() as c_int, dict.as_ptr() as *const c_char, dict.len() as c_int, ) }; if result < 0 { Err(Error::new(ErrorKind::DecompressionFailed)) } else { Ok(result as usize) } } #[derive(Clone)] pub struct ExtState(RefCell>); impl ExtState { fn new() -> Self { let size = size_of_state() + 1; Self(RefCell::new(vec![0; size].into_boxed_slice())) } pub fn with(f: F) -> R where F: FnOnce(&Self, bool) -> R, { EXT_STATE.with(|state| { let reset = { let mut state = state.borrow_mut(); let last = state.len() - 1; if state[last] == 0 { state[last] = 1; false } else { true } }; (f)(state, reset) }) } } impl Deref for ExtState { type Target = RefCell>; fn deref(&self) -> &Self::Target { &self.0 } } thread_local!(static EXT_STATE: ExtState = ExtState::new()); lzzzz-2.0.0/src/lz4/block/mod.rs000064400000000000000000000170001046102023000145430ustar 00000000000000mod api; use crate::{Error, ErrorKind, Result}; use api::ExtState; use std::cmp; /// Calculates the maximum size of the compressed output. /// /// If `original_size` is too large to compress, this returns `0`. #[must_use] pub const fn max_compressed_size(original_size: usize) -> usize { api::compress_bound(original_size) } /// Performs LZ4 block compression. /// /// Ensure that the destination slice has enough capacity. /// If `dst.len()` is smaller than `lz4::max_compressed_size(src.len())`, /// this function may fail. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // The slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let len = lz4::compress(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; /// let compressed = &buf[..len]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress(src: &[u8], dst: &mut [u8], acc: i32) -> Result { compress_to_ptr(src, dst.as_mut_ptr(), dst.len(), acc) } fn compress_to_ptr(src: &[u8], dst: *mut u8, dst_len: usize, acc: i32) -> Result { if src.is_empty() { return Ok(0); } let acc = cmp::min(acc, 33_554_431); let len = ExtState::with(|state, reset| { let mut state = state.borrow_mut(); if reset { api::compress_fast_ext_state_fast_reset(&mut state, src, dst, dst_len, acc) } else { api::compress_fast_ext_state(&mut state, src, dst, dst_len, acc) } }); if len > 0 { Ok(len) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } /// Appends compressed data to `Vec`. /// /// Returns the number of bytes appended to the given `Vec`. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = Vec::new(); /// /// lz4::compress_to_vec(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; /// # let compressed = &buf; /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress_to_vec(src: &[u8], dst: &mut Vec, acc: i32) -> Result { let orig_len = dst.len(); dst.reserve(max_compressed_size(src.len())); #[allow(unsafe_code)] unsafe { let result = compress_to_ptr( src, dst.as_mut_ptr().add(orig_len), dst.capacity() - orig_len, acc, ); dst.set_len(orig_len + result.as_ref().unwrap_or(&0)); result } } /// Compress data to fill `dst`. /// /// This function either compresses the entire `src` buffer into `dst` if it's /// large enough, or will fill `dst` with as much data as possible from `src`. /// /// Returns a pair `(read, wrote)` giving the number of bytes read from `src` /// and the number of bytes written to `dst`. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // This slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let (read, wrote) = lz4::compress_fill(data, &mut buf)?; /// assert_eq!(read, data.len()); /// let compressed = &buf[..wrote]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// /// // This slice doesn't have enough capacity, but we can fill it. /// let mut smallbuf = [0u8; 32]; /// assert!(smallbuf.len() < lz4::max_compressed_size(data.len())); /// /// let (read, wrote) = lz4::compress_fill(data, &mut smallbuf)?; /// assert_eq!(wrote, smallbuf.len()); /// let remaining_data = &data[read..]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(&smallbuf, &mut buf)?; /// # assert_eq!(&buf[..len], &data[..read]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress_fill(src: &[u8], dst: &mut [u8]) -> Result<(usize, usize)> { api::compress_dest_size(src, dst) } /// Decompresses an LZ4 block. /// /// The length of the destination slice must be equal to the original data length. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// const ORIGINAL_SIZE: usize = 44; /// const COMPRESSED_DATA: &str = /// "8B1UaGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLg=="; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// let mut buf = [0u8; ORIGINAL_SIZE]; /// /// lz4::decompress(&data[..], &mut buf[..])?; /// /// assert_eq!( /// &buf[..], /// &b"The quick brown fox jumps over the lazy dog."[..] /// ); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn decompress(src: &[u8], dst: &mut [u8]) -> Result { api::decompress_safe(src, dst) } /// Decompresses an LZ4 block until the destination slice fills up. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// const ORIGINAL_SIZE: usize = 44; /// const COMPRESSED_DATA: &str = /// "8B1UaGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLg=="; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// let mut buf = [0u8; 24]; /// /// lz4::decompress_partial(&data[..], &mut buf[..], ORIGINAL_SIZE)?; /// /// assert_eq!(&buf[..], &b"The quick brown fox jump"[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn decompress_partial(src: &[u8], dst: &mut [u8], original_size: usize) -> Result { api::decompress_safe_partial(src, dst, original_size) } /// Decompresses an LZ4 block with a dictionary. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// const ORIGINAL_SIZE: usize = 44; /// const COMPRESSED_DATA: &str = "DywAFFAgZG9nLg=="; /// const DICT_DATA: &[u8] = b"The quick brown fox jumps over the lazy cat."; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// let mut buf = [0u8; ORIGINAL_SIZE]; /// /// lz4::decompress_with_dict(&data[..], &mut buf[..], DICT_DATA)?; /// /// assert_eq!( /// &buf[..], /// &b"The quick brown fox jumps over the lazy dog."[..] /// ); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn decompress_with_dict(src: &[u8], dst: &mut [u8], dict: &[u8]) -> Result { api::decompress_safe_using_dict(src, dst, dict) } /// Decompresses an LZ4 block with a dictionary until the destination slice fills up. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// const ORIGINAL_SIZE: usize = 44; /// const COMPRESSED_DATA: &str = "DywAFFAgZG9nLg=="; /// const DICT_DATA: &[u8] = b"The quick brown fox jumps over the lazy cat."; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// let mut buf = [0u8; 24]; /// /// lz4::decompress_partial_with_dict(&data[..], &mut buf[..], ORIGINAL_SIZE, DICT_DATA)?; /// /// assert_eq!( /// &buf[..], /// &b"The quick brown fox jump"[..] /// ); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn decompress_partial_with_dict( src: &[u8], dst: &mut [u8], original_size: usize, dict: &[u8], ) -> Result { api::decompress_safe_partial_using_dict(src, dst, original_size, dict) } lzzzz-2.0.0/src/lz4/mod.rs000064400000000000000000000020461046102023000134550ustar 00000000000000//! LZ4 compression and decompression. //! //! LZ4: Extremely fast compression algorithm. //! //! # Acceleration factor //! Some functions take the acceleration factor. //! //! Larger value increases the processing speed in exchange for the //! lesser compression ratio. //! //! ``` //! # use lzzzz::lz4; //! # let data = b"The quick brown fox jumps over the lazy dog."; //! # let mut buf = Vec::new(); //! // The default factor is 1 so both have the same meaning. //! lz4::compress_to_vec(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; //! lz4::compress_to_vec(data, &mut buf, 1)?; //! //! // Factors lower than 1 are interpreted as 1 so these are also the same as above. //! lz4::compress_to_vec(data, &mut buf, 0)?; //! lz4::compress_to_vec(data, &mut buf, -100)?; //! //! // Faster but less effective compression. //! lz4::compress_to_vec(data, &mut buf, 1000)?; //! //! # Ok::<(), std::io::Error>(()) //! ``` mod binding; mod block; mod stream; pub use block::*; pub use stream::*; /// Predefined acceleration level (1). pub const ACC_LEVEL_DEFAULT: i32 = 1; lzzzz-2.0.0/src/lz4/stream/api.rs000064400000000000000000000114071046102023000147430ustar 00000000000000#![allow(unsafe_code)] use super::super::{ binding, binding::{LZ4DecStream, LZ4Stream}, }; use crate::{Error, ErrorKind, Result}; use std::{ mem::{size_of, MaybeUninit}, os::raw::{c_char, c_int, c_void}, ptr::NonNull, ptr::null_mut }; #[allow(clippy::large_enum_variant)] enum Stream { Stack(LZ4Stream), Heap(NonNull), } pub struct CompressionContext { stream: Stream, } unsafe impl Send for CompressionContext {} impl CompressionContext { pub fn new() -> Result { let mut stream = MaybeUninit::::uninit(); unsafe { let ptr = binding::LZ4_initStream( stream.as_mut_ptr() as *mut c_void, size_of::(), ); if !ptr.is_null() { return Ok(Self { stream: Stream::Stack(stream.assume_init()), }); } NonNull::new(binding::LZ4_createStream()) } .ok_or_else(|| Error::new(ErrorKind::InitializationFailed)) .map(|stream| Self { stream: Stream::Heap(stream), }) } fn get_ptr(&mut self) -> *mut LZ4Stream { match &mut self.stream { Stream::Stack(stream) => stream as *mut LZ4Stream, Stream::Heap(ptr) => ptr.as_ptr(), } } pub fn next(&mut self, src: &[u8], dst: *mut u8, dst_len: usize, acceleration: i32) -> usize { unsafe { binding::LZ4_compress_fast_continue( self.get_ptr(), src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, acceleration as c_int, ) as usize } } pub fn load_dict(&mut self, dict: &[u8]) { unsafe { binding::LZ4_loadDict( self.get_ptr(), dict.as_ptr() as *const c_char, dict.len() as c_int, ); } } pub fn load_dict_slow(&mut self, dict: &[u8]) { unsafe { binding::LZ4_loadDictSlow( self.get_ptr(), dict.as_ptr() as *const c_char, dict.len() as c_int, ); } } pub fn save_dict(&mut self, dict: &mut [u8]) { unsafe { binding::LZ4_saveDict( self.get_ptr(), dict.as_ptr() as *mut c_char, dict.len() as c_int, ); } } pub fn attach_dict(&mut self, dict_stream: Option<&mut CompressionContext>) { unsafe { if dict_stream.is_none() { // Note(sewer56): When detaching dictionary, we need to reset the stream state // This behaviour is consistent with what the LZ4 library itself does internally. binding::LZ4_resetStream_fast(self.get_ptr()); } let dict_ptr = dict_stream.map(|ctx| ctx.get_ptr()).unwrap_or(null_mut()); binding::LZ4_attach_dictionary(self.get_ptr(), dict_ptr); } } } impl Drop for CompressionContext { fn drop(&mut self) { if let Stream::Heap(mut ptr) = self.stream { unsafe { binding::LZ4_freeStream(ptr.as_mut()); } } } } pub struct DecompressionContext { stream: NonNull, } unsafe impl Send for DecompressionContext {} impl DecompressionContext { pub fn new() -> Result { unsafe { let ptr = NonNull::new(binding::LZ4_createStreamDecode()); ptr.ok_or_else(|| Error::new(ErrorKind::InitializationFailed)) .map(|stream| Self { stream }) } } pub fn reset(&mut self, dict: &[u8]) -> Result<()> { let result = unsafe { binding::LZ4_setStreamDecode( self.stream.as_ptr(), dict.as_ptr() as *const c_char, dict.len() as c_int, ) }; if result == 1 { Ok(()) } else { Err(Error::new(ErrorKind::InitializationFailed)) } } pub fn decompress(&mut self, src: &[u8], dst: *mut u8, dst_len: usize) -> Result { let result = unsafe { binding::LZ4_decompress_safe_continue( self.stream.as_ptr(), src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, ) }; if result < 0 { Err(Error::new(ErrorKind::DecompressionFailed)) } else { Ok(result as usize) } } } impl Drop for DecompressionContext { fn drop(&mut self) { unsafe { binding::LZ4_freeStreamDecode(self.stream.as_mut()); } } } lzzzz-2.0.0/src/lz4/stream/mod.rs000064400000000000000000000173321046102023000147540ustar 00000000000000mod api; use crate::{ common::{DEFAULT_BUF_SIZE, DICTIONARY_SIZE}, lz4, Error, ErrorKind, Result, }; use api::{CompressionContext, DecompressionContext}; use std::{borrow::Cow, cmp, collections::LinkedList, pin::Pin}; /// Streaming LZ4 compressor. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // The slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let mut comp = lz4::Compressor::new()?; /// let len = comp.next(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; /// let compressed = &buf[..len]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub struct Compressor<'a> { ctx: CompressionContext, dict: Pin>, safe_buf: Vec, } impl<'a> Compressor<'a> { /// Creates a new `Compressor`. pub fn new() -> Result { Ok(Self { ctx: CompressionContext::new()?, dict: Pin::new(Cow::Borrowed(&[])), safe_buf: Vec::new(), }) } /// Creates a new `Compressor` with a dictionary. pub fn with_dict(dict: D) -> Result where D: Into>, { let mut comp = Self { dict: Pin::new(dict.into()), ..Self::new()? }; comp.ctx.load_dict(&comp.dict); Ok(comp) } /// Creates a new `Compressor` with a dictionary. /// This variant which consumes more initialization time to better reference the dictionary, /// resulting in slightly improved compression ratios at expense of time. pub fn with_dict_slow(dict: D) -> Result where D: Into>, { let mut comp = Self { dict: Pin::new(dict.into()), ..Self::new()? }; comp.ctx.load_dict_slow(&comp.dict); Ok(comp) } /// Performs LZ4 streaming compression. /// /// Returns the number of bytes written into the destination buffer. pub fn next(&mut self, src: &[u8], dst: &mut [u8], acc: i32) -> Result { self.next_to_ptr(src, dst.as_mut_ptr(), dst.len(), acc) } fn next_to_ptr(&mut self, src: &[u8], dst: *mut u8, dst_len: usize, acc: i32) -> Result { let is_empty = src.is_empty() && dst_len == 0; let dst_len = self.ctx.next(src, dst, dst_len, acc); self.save_dict(); if dst_len > 0 { Ok(dst_len) } else if is_empty { Ok(0) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } /// Appends compressed data to `Vec`. /// /// Returns the number of bytes appended to the given `Vec`. pub fn next_to_vec(&mut self, src: &[u8], dst: &mut Vec, acc: i32) -> Result { let orig_len = dst.len(); dst.reserve(lz4::max_compressed_size(src.len())); #[allow(unsafe_code)] unsafe { let result = self.next_to_ptr( src, dst.as_mut_ptr().add(orig_len), dst.capacity() - orig_len, acc, ); dst.set_len(orig_len + result.as_ref().unwrap_or(&0)); result } } fn save_dict(&mut self) { self.safe_buf.resize(DICTIONARY_SIZE, 0); self.ctx.save_dict(&mut self.safe_buf); } /// Attaches a dictionary stream for efficient dictionary reuse. /// /// This allows efficient re-use of a static dictionary multiple times by referencing /// the dictionary stream in-place rather than copying it. /// /// # Arguments /// /// * `dict_stream` - The dictionary stream to attach, or None to unset any existing dictionary /// /// # Notes /// /// - The dictionary stream must have been prepared using `with_dict()` or `with_dict_slow()` /// - The dictionary will only remain attached through the first compression call /// - The dictionary stream (and its source buffer) must remain valid through the compression session /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// let dict_data = b"some dictionary data"; /// let data = b"data to compress"; /// /// // Create dictionary stream /// let mut dict_comp = lz4::Compressor::with_dict(dict_data)?; /// /// // Create working stream and attach dictionary /// let mut comp = lz4::Compressor::new()?; /// comp.attach_dict(Some(&mut dict_comp)); /// /// // Compress data using the attached dictionary /// let mut buf = [0u8; 256]; /// let len = comp.next(data, &mut buf, lz4::ACC_LEVEL_DEFAULT)?; /// # Ok::<(), std::io::Error>(()) /// ``` pub fn attach_dict(&mut self, dict_stream: Option<&mut Compressor<'a>>) { if let Some(dict) = dict_stream { self.ctx.attach_dict(Some(&mut dict.ctx)); } else { self.ctx.attach_dict(None); } } } /// Streaming LZ4 decompressor. /// /// # Example /// /// ``` /// use lzzzz::lz4; /// /// const ORIGINAL_SIZE: usize = 44; /// const COMPRESSED_DATA: &str = /// "8B1UaGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLg=="; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// /// let mut decomp = lz4::Decompressor::new()?; /// let result = decomp.next(&data[..], ORIGINAL_SIZE)?; /// /// assert_eq!(result, &b"The quick brown fox jumps over the lazy dog."[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub struct Decompressor<'a> { ctx: DecompressionContext, cache: LinkedList>, cache_len: usize, last_len: usize, dict: Pin>, } impl<'a> Decompressor<'a> { /// Creates a new `Decompressor`. pub fn new() -> Result { Ok(Self { ctx: DecompressionContext::new()?, cache: LinkedList::new(), cache_len: 0, last_len: 0, dict: Pin::new(Cow::Borrowed(&[])), }) } /// Creates a new `Decompressor` with a dictionary. pub fn with_dict(dict: D) -> Result where D: Into>, { let mut decomp = Self { dict: Pin::new(dict.into()), ..Self::new()? }; decomp.ctx.reset(&decomp.dict)?; Ok(decomp) } /// Decompresses an LZ4 block. pub fn next(&mut self, src: &[u8], original_size: usize) -> Result<&[u8]> { if self .cache .back() .map(|v| v.capacity() - v.len()) .filter(|n| *n >= original_size) .is_none() { self.cache.push_back(Vec::with_capacity(cmp::max( original_size, DEFAULT_BUF_SIZE, ))); } let back = self.cache.back_mut().unwrap(); let orig_len = back.len(); #[allow(unsafe_code)] unsafe { let dst_len = self.ctx.decompress( src, back.as_mut_ptr().add(orig_len), back.capacity() - orig_len, )?; back.set_len(orig_len + dst_len); self.cache_len += dst_len; } self.last_len = original_size; while let Some(len) = self .cache .front() .map(Vec::len) .filter(|n| self.cache_len - n >= DICTIONARY_SIZE) { self.cache.pop_front(); self.cache_len -= len; } Ok(self.data()) } fn data(&self) -> &[u8] { if let Some(back) = self.cache.back() { let offset = back.len() - self.last_len; &back[offset..] } else { &[] } } } lzzzz-2.0.0/src/lz4_hc/binding.rs000064400000000000000000000043661046102023000147710ustar 00000000000000use std::os::raw::{c_char, c_int, c_void}; const LZ4HC_HASH_LOG: usize = 15; const LZ4HC_HASHTABLESIZE: usize = 1 << LZ4HC_HASH_LOG; const LZ4HC_DICTIONARY_LOGSIZE: usize = 16; const LZ4HC_MAXD: usize = 1 << LZ4HC_DICTIONARY_LOGSIZE; pub const LZ4_STREAMHCSIZE: usize = 4 * LZ4HC_HASHTABLESIZE + 2 * LZ4HC_MAXD + 56; #[repr(C)] pub struct LZ4StreamHC { _private: [u8; 0], } extern "C" { pub fn LZ4_compress_HC_extStateHC( state: *mut c_void, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, compression_level: c_int, ) -> c_int; pub fn LZ4_compress_HC_extStateHC_fastReset( state: *mut c_void, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, compression_level: c_int, ) -> c_int; pub fn LZ4_compress_HC_destSize( state: *mut c_void, src: *const c_char, dst: *mut c_char, src_size_ptr: *mut c_int, target_dst_dize: c_int, compression_level: c_int, ) -> c_int; pub fn LZ4_createStreamHC() -> *mut LZ4StreamHC; pub fn LZ4_freeStreamHC(ptr: *mut LZ4StreamHC) -> c_int; pub fn LZ4_loadDictHC( ptr: *mut LZ4StreamHC, dictionary: *const c_char, dict_size: c_int, ) -> c_int; pub fn LZ4_saveDictHC( ptr: *mut LZ4StreamHC, safe_buffer: *mut c_char, max_dict_size: c_int, ) -> c_int; pub fn LZ4_compress_HC_continue( ptr: *mut LZ4StreamHC, src: *const c_char, dst: *mut c_char, src_size: c_int, dst_capacity: c_int, ) -> c_int; pub fn LZ4_compress_HC_continue_destSize( ptr: *mut LZ4StreamHC, src: *const c_char, dst: *mut c_char, src_size_ptr: *mut c_int, target_dst_size: c_int, ) -> c_int; pub fn LZ4_setCompressionLevel(ptr: *mut LZ4StreamHC, compression_level: c_int); pub fn LZ4_favorDecompressionSpeed(ptr: *mut LZ4StreamHC, favor: c_int); pub fn LZ4_attach_HC_dictionary( working_stream: *mut LZ4StreamHC, dictionary_stream: *const LZ4StreamHC ); pub fn LZ4_resetStreamHC_fast( streamPtr: *mut LZ4StreamHC, compressionLevel: c_int ); } lzzzz-2.0.0/src/lz4_hc/block/api.rs000064400000000000000000000050071046102023000152130ustar 00000000000000#![allow(unsafe_code)] use super::super::binding; use std::{ cell::RefCell, ops::Deref, os::raw::{c_char, c_int, c_void}, }; pub const fn size_of_state() -> usize { binding::LZ4_STREAMHCSIZE } pub fn compress_ext_state( state: &mut [u8], src: &[u8], dst: *mut u8, dst_len: usize, compression_level: i32, ) -> usize { unsafe { binding::LZ4_compress_HC_extStateHC( state.as_mut_ptr() as *mut c_void, src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, compression_level as c_int, ) as usize } } pub fn compress_ext_state_fast_reset( state: &mut [u8], src: &[u8], dst: *mut u8, dst_len: usize, compression_level: i32, ) -> usize { unsafe { binding::LZ4_compress_HC_extStateHC_fastReset( state.as_mut_ptr() as *mut c_void, src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, compression_level as c_int, ) as usize } } pub fn compress_dest_size( state: &mut [u8], src: &[u8], dst: &mut [u8], compression_level: i32, ) -> (usize, usize) { let mut src_len = src.len() as i32; let dst_len = unsafe { binding::LZ4_compress_HC_destSize( state.as_mut_ptr() as *mut c_void, src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, &mut src_len as *mut c_int, dst.len() as c_int, compression_level as c_int, ) as usize }; (src_len as usize, dst_len) } #[derive(Clone)] pub struct ExtState(RefCell>); impl ExtState { fn new() -> Self { let size = size_of_state() + 1; Self(RefCell::new(vec![0; size].into_boxed_slice())) } pub fn with(f: F) -> R where F: FnOnce(&Self, bool) -> R, { EXT_STATE.with(|state| { let reset = { let mut state = state.borrow_mut(); let last = state.len() - 1; if state[last] == 0 { state[last] = 1; false } else { true } }; (f)(state, reset) }) } } impl Deref for ExtState { type Target = RefCell>; fn deref(&self) -> &Self::Target { &self.0 } } thread_local!(static EXT_STATE: ExtState = ExtState::new()); lzzzz-2.0.0/src/lz4_hc/block/mod.rs000064400000000000000000000134111046102023000152170ustar 00000000000000mod api; use crate::{lz4, Error, ErrorKind, Result}; use api::ExtState; use std::{cmp, io::Cursor}; /// Performs LZ4_HC block compression. /// /// Ensure that the destination slice has enough capacity. /// If `dst.len()` is smaller than `lz4::max_compressed_size(src.len())`, /// this function may fail. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // The slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let len = lz4_hc::compress(data, &mut buf, lz4_hc::CLEVEL_DEFAULT)?; /// let compressed = &buf[..len]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress( /// # compressed, /// # &mut buf[..data.len()], /// # )?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress(src: &[u8], dst: &mut [u8], level: i32) -> Result { compress_to_ptr(src, dst.as_mut_ptr(), dst.len(), level) } fn compress_to_ptr(src: &[u8], dst: *mut u8, dst_len: usize, level: i32) -> Result { if src.is_empty() { return Ok(0); } let len = ExtState::with(|state, reset| { if reset { api::compress_ext_state_fast_reset(&mut state.borrow_mut(), src, dst, dst_len, level) } else { api::compress_ext_state(&mut state.borrow_mut(), src, dst, dst_len, level) } }); if len > 0 { Ok(len) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } /// Compress data to fill `dst`. /// /// This function either compresses the entire `src` buffer into `dst` if it's /// large enough, or will fill `dst` with as much data as possible from `src`. /// /// Returns a pair `(read, wrote)` giving the number of bytes read from `src` /// and the number of bytes written to `dst`. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // This slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let (read, wrote) = lz4_hc::compress_fill(data, &mut buf, lz4_hc::CLEVEL_DEFAULT)?; /// assert_eq!(read, data.len()); /// let compressed = &buf[..wrote]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// /// // This slice doesn't have enough capacity, but we can fill it. /// let mut smallbuf = [0u8; 32]; /// assert!(smallbuf.len() < lz4::max_compressed_size(data.len())); /// /// let (read, wrote) = lz4_hc::compress_fill(data, &mut smallbuf, lz4_hc::CLEVEL_DEFAULT)?; /// assert_eq!(wrote, smallbuf.len()); /// let remaining_data = &data[read..]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(&smallbuf, &mut buf)?; /// # assert_eq!(&buf[..len], &data[..read]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress_fill(src: &[u8], dst: &mut [u8], level: i32) -> Result<(usize, usize)> { if src.is_empty() { return Ok((0, 0)); } let (read, wrote) = ExtState::with(|state, _reset| { api::compress_dest_size(&mut state.borrow_mut(), src, dst, level) }); if wrote > 0 { Ok((read, wrote)) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } /// Compresses data until the destination slice fills up. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// use std::io::Cursor; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 16]; /// /// let mut src = Cursor::new(&data[..]); /// let len = lz4_hc::compress_partial(&mut src, &mut buf, lz4_hc::CLEVEL_DEFAULT)?; /// let compressed = &buf[..len]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress( /// # compressed, /// # &mut buf[..data.len()], /// # )?; /// # assert_eq!(&buf[..len], &data[..src.position() as usize]); /// # Ok::<(), std::io::Error>(()) /// ``` #[deprecated(since = "1.1.0", note = "Use compress_fill instead.")] pub fn compress_partial(src: &mut Cursor, dst: &mut [u8], level: i32) -> Result where T: AsRef<[u8]>, { let src_ref = src.get_ref().as_ref(); let pos = cmp::min(src_ref.len(), src.position() as usize); let src_ref = &src_ref[pos..]; if src_ref.is_empty() || dst.is_empty() { return Ok(0); } let (src_len, dst_len) = ExtState::with(|state, _| { api::compress_dest_size(&mut state.borrow_mut(), src_ref, dst, level) }); src.set_position(src.position() + src_len as u64); Ok(dst_len) } /// Appends compressed data to `Vec`. /// /// Returns the number of bytes appended to the given `Vec`. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = Vec::new(); /// /// lz4_hc::compress_to_vec(data, &mut buf, lz4_hc::CLEVEL_DEFAULT)?; /// /// # let compressed = &buf; /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress( /// # compressed, /// # &mut buf[..data.len()], /// # )?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress_to_vec(src: &[u8], dst: &mut Vec, level: i32) -> Result { let orig_len = dst.len(); dst.reserve(lz4::max_compressed_size(src.len())); #[allow(unsafe_code)] unsafe { let result = compress_to_ptr( src, dst.as_mut_ptr().add(orig_len), dst.capacity() - orig_len, level, ); dst.set_len(orig_len + result.as_ref().unwrap_or(&0)); result } } lzzzz-2.0.0/src/lz4_hc/mod.rs000064400000000000000000000015051046102023000141260ustar 00000000000000//! LZ4_HC compression. //! //! LZ4_HC: High compression variant of LZ4. //! //! # Decompression //! The `lz4_hc` module doesn't provide decompression functionality. //! Use the [`lz4`] module instead. //! //! [`lz4`]: ../lz4/index.html mod binding; mod block; mod stream; pub use block::*; pub use stream::*; /// Predefined compression level (3). pub const CLEVEL_MIN: i32 = 3; /// Predefined compression level (9). pub const CLEVEL_DEFAULT: i32 = 9; /// Predefined compression level (10). pub const CLEVEL_OPT_MIN: i32 = 10; /// Predefined compression level (12). pub const CLEVEL_MAX: i32 = 12; /// Decompression speed mode flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum FavorDecSpeed { Disabled, Enabled, } impl Default for FavorDecSpeed { fn default() -> Self { Self::Disabled } } lzzzz-2.0.0/src/lz4_hc/stream/api.rs000064400000000000000000000072051046102023000154160ustar 00000000000000#![allow(unsafe_code)] use super::super::{binding, binding::LZ4StreamHC}; use crate::{Error, ErrorKind, Result}; use std::{ os::raw::{c_char, c_int}, ptr::NonNull, ptr::null_mut }; pub struct CompressionContext { stream: NonNull, } unsafe impl Send for CompressionContext {} impl CompressionContext { pub fn new() -> Result { let ptr = unsafe { NonNull::new(binding::LZ4_createStreamHC()) }; ptr.ok_or_else(|| Error::new(ErrorKind::InitializationFailed)) .map(|stream| Self { stream }) } pub fn set_compression_level(&mut self, compression_level: i32) { unsafe { binding::LZ4_setCompressionLevel(self.stream.as_ptr(), compression_level as c_int) } } pub fn set_favor_dec_speed(&mut self, flag: bool) { unsafe { binding::LZ4_favorDecompressionSpeed(self.stream.as_ptr(), i32::from(flag)) } } pub fn load_dict(&mut self, dict: &[u8]) { unsafe { binding::LZ4_loadDictHC( self.stream.as_ptr(), dict.as_ptr() as *const c_char, dict.len() as c_int, ); } } pub fn save_dict(&mut self, dict: &mut [u8]) { unsafe { binding::LZ4_saveDictHC( self.stream.as_ptr(), dict.as_ptr() as *mut c_char, dict.len() as c_int, ); } } pub fn next(&mut self, src: &[u8], dst: *mut u8, dst_len: usize) -> Result { if src.is_empty() { return Ok(0); } let dst_len = unsafe { binding::LZ4_compress_HC_continue( self.stream.as_ptr(), src.as_ptr() as *const c_char, dst as *mut c_char, src.len() as c_int, dst_len as c_int, ) as usize }; if dst_len > 0 { Ok(dst_len) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } pub fn next_partial(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize)> { if src.is_empty() || dst.is_empty() { return Ok((0, 0)); } let mut src_len = src.len() as c_int; let dst_len = unsafe { binding::LZ4_compress_HC_continue_destSize( self.stream.as_ptr(), src.as_ptr() as *const c_char, dst.as_mut_ptr() as *mut c_char, &mut src_len as *mut c_int, dst.len() as c_int, ) as usize }; if dst_len > 0 { Ok((src_len as usize, dst_len)) } else { Err(Error::new(ErrorKind::CompressionFailed)) } } pub fn attach_dict(&mut self, dict_stream: Option<&CompressionContext>, compression_level: i32) { unsafe { if dict_stream.is_none() { // Note(sewer56): When detaching dictionary, we need to reset the stream state // This behaviour is consistent with what the LZ4 library itself does internally. // The LZ4HC API does not have a way to retrieve compression level, so we must pass it manually, // since the HC API differs here. binding::LZ4_resetStreamHC_fast(self.stream.as_ptr(), compression_level); } let dict_ptr = dict_stream.map(|ctx| ctx.stream.as_ptr()).unwrap_or(null_mut()); binding::LZ4_attach_HC_dictionary(self.stream.as_ptr(), dict_ptr); } } } impl Drop for CompressionContext { fn drop(&mut self) { unsafe { binding::LZ4_freeStreamHC(self.stream.as_mut()); } } } lzzzz-2.0.0/src/lz4_hc/stream/mod.rs000064400000000000000000000157241046102023000154310ustar 00000000000000mod api; use crate::{common::DICTIONARY_SIZE, lz4, lz4_hc::FavorDecSpeed, Result}; use api::CompressionContext; use std::{borrow::Cow, cmp, io::Cursor, pin::Pin}; /// Streaming LZ4_HC compressor. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 256]; /// /// // The slice should have enough capacity. /// assert!(buf.len() >= lz4::max_compressed_size(data.len())); /// /// let mut comp = lz4_hc::Compressor::new()?; /// let len = comp.next(data, &mut buf)?; /// let compressed = &buf[..len]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(compressed, &mut buf[..data.len()])?; /// # assert_eq!(&buf[..len], &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub struct Compressor<'a> { ctx: CompressionContext, dict: Pin>, safe_buf: Vec, } impl<'a> Compressor<'a> { /// Creates a new `Compressor`. pub fn new() -> Result { Ok(Self { ctx: CompressionContext::new()?, dict: Pin::new(Cow::Borrowed(&[])), safe_buf: Vec::new(), }) } /// Creates a new `Compressor` with a dictionary. pub fn with_dict(dict: D, compression_level: i32) -> Result where D: Into>, { // Note(sewer56). // The LZ4 documentation states the following: // - In order for LZ4_loadDictHC() to create the correct data structure, // it is essential to set the compression level _before_ loading the dictionary. // Therefore this API requires a `compression_level`. let mut comp = Self { dict: Pin::new(dict.into()), ..Self::new()? }; comp.ctx.set_compression_level(compression_level); comp.ctx.load_dict(&comp.dict); Ok(comp) } /// Sets the compression level. pub fn set_compression_level(&mut self, level: i32) { self.ctx.set_compression_level(level); } /// Sets the decompression speed mode flag. pub fn set_favor_dec_speed(&mut self, dec_speed: FavorDecSpeed) { self.ctx .set_favor_dec_speed(dec_speed == FavorDecSpeed::Enabled); } /// Performs LZ4_HC streaming compression. /// /// Returns the number of bytes written into the destination buffer. pub fn next(&mut self, src: &[u8], dst: &mut [u8]) -> Result { self.next_to_ptr(src, dst.as_mut_ptr(), dst.len()) } fn next_to_ptr(&mut self, src: &[u8], dst: *mut u8, dst_len: usize) -> Result { let result = self.ctx.next(src, dst, dst_len)?; self.save_dict(); Ok(result) } /// Performs LZ4_HC streaming compression to fill `dst`. /// /// This function either compresses the entire `src` buffer into `dst` if it's /// large enough, or will fill `dst` with as much data as possible from `src`. /// /// Returns a pair `(read, wrote)` giving the number of bytes read from `src` /// and the number of bytes written to `dst`. /// /// # Example /// /// ``` /// use lzzzz::{lz4, lz4_hc}; /// /// let data = b"The quick brown fox jumps over the lazy dog."; /// /// let mut smallbuf = [0u8; 32]; /// assert!(smallbuf.len() < lz4::max_compressed_size(data.len())); /// /// let mut comp = lz4_hc::Compressor::new()?; /// let (read, wrote) = comp.next_fill(data, &mut smallbuf)?; /// let remaining_data = &data[read..]; /// /// # let mut buf = [0u8; 256]; /// # let len = lz4::decompress(&smallbuf, &mut buf)?; /// # assert_eq!(&buf[..len], &data[..read]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn next_fill(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize)> { let (src_len, dst_len) = self.ctx.next_partial(src, dst)?; self.save_dict(); Ok((src_len, dst_len)) } /// Compresses data until the destination slice fills up. /// /// Returns the number of bytes written into the destination buffer. #[deprecated(since = "1.1.0", note = "Use next_fill instead.")] pub fn next_partial(&mut self, src: &mut Cursor, dst: &mut [u8]) -> Result where T: AsRef<[u8]>, { let src_ref = src.get_ref().as_ref(); let pos = cmp::min(src_ref.len(), src.position() as usize); let src_ref = &src_ref[pos..]; let (src_len, dst_len) = self.ctx.next_partial(src_ref, dst)?; src.set_position(src.position() + src_len as u64); self.save_dict(); Ok(dst_len) } /// Appends a compressed frame to `Vec`. /// /// Returns the number of bytes appended to the given `Vec`. pub fn next_to_vec(&mut self, src: &[u8], dst: &mut Vec) -> Result { let orig_len = dst.len(); dst.reserve(lz4::max_compressed_size(src.len())); #[allow(unsafe_code)] unsafe { let result = self.next_to_ptr( src, dst.as_mut_ptr().add(orig_len), dst.capacity() - orig_len, ); dst.set_len(orig_len + result.as_ref().unwrap_or(&0)); result } } fn save_dict(&mut self) { self.safe_buf.resize(DICTIONARY_SIZE, 0); self.ctx.save_dict(&mut self.safe_buf); } /// Attaches a dictionary stream for efficient dictionary reuse. /// /// This allows efficient re-use of a static dictionary multiple times by referencing /// the dictionary stream in-place rather than copying it. /// /// # Arguments /// /// * `dict_stream` - The dictionary stream to attach, or None to unset any existing dictionary /// * `compression_level` - The compression level to use (CLEVEL_MIN to CLEVEL_MAX) /// /// # Notes /// /// - The dictionary stream must have been prepared using `with_dict()` /// - The dictionary will only remain attached through the first compression call /// - The dictionary stream (and its source buffer) must remain valid through the compression session /// /// # Example /// /// ``` /// use lzzzz::lz4_hc; /// /// let dict_data = b"dictionary data"; /// let data = b"data to compress"; /// /// // Create dictionary stream /// let dict_comp = lz4_hc::Compressor::with_dict(dict_data, lz4_hc::CLEVEL_DEFAULT)?; /// /// // Create working stream and attach dictionary /// let mut comp = lz4_hc::Compressor::new()?; /// comp.attach_dict(Some(&dict_comp), lz4_hc::CLEVEL_DEFAULT); /// /// // Compress data using the attached dictionary /// let mut buf = [0u8; 256]; /// let len = comp.next(data, &mut buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` pub fn attach_dict(&mut self, dict_stream: Option<&Compressor<'a>>, compression_level: i32) { if let Some(dict) = dict_stream { self.ctx.attach_dict(Some(&dict.ctx), compression_level); } else { self.ctx.attach_dict(None, compression_level); } } } lzzzz-2.0.0/src/lz4f/api.rs000064400000000000000000000203531046102023000136160ustar 00000000000000#![allow(unsafe_code)] use super::{ binding, binding::{ LZ4FCompressionCtx, LZ4FCompressionDict, LZ4FCompressionOptions, LZ4FDecompressionCtx, LZ4FDecompressionOptions, }, Dictionary, }; use crate::lz4f::{Error, ErrorKind, FrameInfo, Preferences, Result}; use std::{mem::MaybeUninit, os::raw::c_void, ptr::NonNull}; pub const LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH: usize = 5; pub const LZ4F_HEADER_SIZE_MAX: usize = 19; pub struct CompressionContext { ctx: NonNull, dict: Option, } unsafe impl Send for CompressionContext {} impl CompressionContext { pub fn new(dict: Option) -> Result { let ctx = MaybeUninit::<*mut LZ4FCompressionCtx>::uninit(); unsafe { let code = binding::LZ4F_createCompressionContext( ctx.as_ptr() as *mut *mut binding::LZ4FCompressionCtx, binding::LZ4F_getVersion(), ); result_from_code(code).and_then(|_| { Ok(Self { ctx: NonNull::new(ctx.assume_init()) .ok_or_else(|| crate::Error::new(crate::ErrorKind::InitializationFailed))?, dict, }) }) } } pub fn begin(&mut self, dst: *mut u8, dst_len: usize, prefs: &Preferences) -> Result { let code = unsafe { if let Some(dict) = &self.dict { binding::LZ4F_compressBegin_usingCDict( self.ctx.as_ptr(), dst as *mut c_void, dst_len, dict.handle().0.as_ptr(), prefs as *const Preferences, ) } else { binding::LZ4F_compressBegin(self.ctx.as_ptr(), dst as *mut c_void, dst_len, prefs) } }; result_from_code(code).map(|_| code) } pub fn update( &mut self, dst: *mut u8, dst_len: usize, src: &[u8], stable_src: bool, ) -> Result { let opt = LZ4FCompressionOptions::stable(stable_src); let code = unsafe { binding::LZ4F_compressUpdate( self.ctx.as_ptr(), dst as *mut c_void, dst_len, src.as_ptr() as *const c_void, src.len(), &opt as *const LZ4FCompressionOptions, ) }; result_from_code(code).map(|_| code) } pub fn flush(&mut self, dst: *mut u8, dst_len: usize, stable_src: bool) -> Result { let opt = LZ4FCompressionOptions::stable(stable_src); let code = unsafe { binding::LZ4F_flush( self.ctx.as_ptr(), dst as *mut c_void, dst_len, &opt as *const LZ4FCompressionOptions, ) }; result_from_code(code).map(|_| code) } pub fn end(&mut self, dst: *mut u8, dst_len: usize, stable_src: bool) -> Result { let opt = LZ4FCompressionOptions::stable(stable_src); let code = unsafe { binding::LZ4F_compressEnd( self.ctx.as_ptr(), dst as *mut c_void, dst_len, &opt as *const LZ4FCompressionOptions, ) }; result_from_code(code).map(|_| code) } pub fn compress_bound(src_size: usize, prefs: &Preferences) -> usize { unsafe { binding::LZ4F_compressBound(src_size, prefs as *const Preferences) } } } impl Drop for CompressionContext { fn drop(&mut self) { unsafe { binding::LZ4F_freeCompressionContext(self.ctx.as_ptr()); } } } pub struct DecompressionContext { ctx: NonNull, } unsafe impl Send for DecompressionContext {} impl DecompressionContext { pub fn new() -> Result { let ctx = MaybeUninit::<*mut LZ4FDecompressionCtx>::uninit(); unsafe { let code = binding::LZ4F_createDecompressionContext( ctx.as_ptr() as *mut *mut binding::LZ4FDecompressionCtx, binding::LZ4F_getVersion(), ); result_from_code(code).and_then(|_| { Ok(Self { ctx: NonNull::new(ctx.assume_init()) .ok_or_else(|| crate::Error::new(crate::ErrorKind::InitializationFailed))?, }) }) } } pub fn get_frame_info(&self, src: &[u8]) -> Result<(FrameInfo, usize)> { let mut info = MaybeUninit::::uninit(); let mut src_len = src.len(); let code = unsafe { binding::LZ4F_getFrameInfo( self.ctx.as_ptr(), info.as_mut_ptr(), src.as_ptr() as *const c_void, &mut src_len as *mut usize, ) }; result_from_code(code).map(|_| (unsafe { info.assume_init() }, src_len)) } pub fn decompress_dict( &mut self, src: &[u8], dst: &mut [u8], dict: &[u8], stable_dst: bool, ) -> Result<(usize, usize, usize)> { let mut dst_len = dst.len(); let mut src_len = src.len(); let opt = LZ4FDecompressionOptions::stable(stable_dst); let code = unsafe { binding::LZ4F_decompress_usingDict( self.ctx.as_ptr(), dst.as_mut_ptr() as *mut c_void, &mut dst_len as *mut usize, src.as_ptr() as *const c_void, &mut src_len as *mut usize, dict.as_ptr() as *const c_void, dict.len(), &opt as *const LZ4FDecompressionOptions, ) }; result_from_code(code).map(|_| (src_len, dst_len, code)) } pub fn reset(&mut self) { unsafe { binding::LZ4F_resetDecompressionContext(self.ctx.as_ptr()); } } } impl Drop for DecompressionContext { fn drop(&mut self) { unsafe { binding::LZ4F_freeDecompressionContext(self.ctx.as_ptr()); } } } pub fn compress_frame_bound(src_size: usize, prefs: &Preferences) -> usize { unsafe { binding::LZ4F_compressFrameBound(src_size, prefs as *const Preferences) } } pub fn header_size(src: &[u8]) -> usize { unsafe { binding::LZ4F_headerSize(src.as_ptr() as *const c_void, src.len()) } } pub fn compress(src: &[u8], dst: *mut u8, dst_len: usize, prefs: &Preferences) -> Result { let code = unsafe { binding::LZ4F_compressFrame( dst as *mut c_void, dst_len, src.as_ptr() as *const c_void, src.len(), prefs as *const Preferences, ) }; result_from_code(code).map(|_| code) } fn result_from_code(code: usize) -> Result<()> { Err(Error::new(match code.wrapping_neg() { 1 => ErrorKind::Generic, 2 => ErrorKind::MaxBlockSizeInvalid, 3 => ErrorKind::BlockModeInvalid, 4 => ErrorKind::ContentChecksumFlagInvalid, 5 => ErrorKind::CompressionLevelInvalid, 6 => ErrorKind::HeaderVersionWrong, 7 => ErrorKind::BlockChecksumInvalid, 8 => ErrorKind::ReservedFlagSet, 9 => ErrorKind::AllocationFailed, 10 => ErrorKind::SrcSizeTooLarge, 11 => ErrorKind::DstMaxSizeTooSmall, 12 => ErrorKind::FrameHeaderIncomplete, 13 => ErrorKind::FrameTypeUnknown, 14 => ErrorKind::FrameSizeWrong, 15 => ErrorKind::SrcPtrWrong, 16 => ErrorKind::DecompressionFailed, 17 => ErrorKind::HeaderChecksumInvalid, 18 => ErrorKind::ContentChecksumInvalid, 19 => ErrorKind::FrameDecodingAlreadyStarted, _ => return Ok(()), })) } pub struct DictionaryHandle(NonNull); unsafe impl Send for DictionaryHandle {} unsafe impl Sync for DictionaryHandle {} impl DictionaryHandle { pub fn new(data: &[u8]) -> Result { let dict = unsafe { binding::LZ4F_createCDict(data.as_ptr() as *const c_void, data.len()) }; NonNull::new(dict) .ok_or_else(|| crate::Error::new(crate::ErrorKind::InitializationFailed).into()) .map(Self) } } impl Drop for DictionaryHandle { fn drop(&mut self) { unsafe { binding::LZ4F_freeCDict(self.0.as_ptr()); } } } lzzzz-2.0.0/src/lz4f/binding.rs000064400000000000000000000073001046102023000144540ustar 00000000000000use super::{FrameInfo, Preferences}; use std::os::raw::{c_uint, c_void}; #[allow(non_camel_case_types)] type size_t = usize; #[repr(C)] pub struct LZ4FCompressionCtx { _private: [u8; 0], } #[repr(C)] pub struct LZ4FDecompressionCtx { _private: [u8; 0], } #[repr(C)] pub struct LZ4FCompressionDict { _private: [u8; 0], } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct LZ4FCompressionOptions { pub stable_src: c_uint, pub _reserved: [c_uint; 3], } impl LZ4FCompressionOptions { pub fn stable(stable: bool) -> Self { Self { stable_src: u32::from(stable), ..Default::default() } } } #[derive(Debug, Default, Copy, Clone)] #[repr(C)] pub struct LZ4FDecompressionOptions { pub stable_dst: c_uint, pub _reserved: [c_uint; 3], } impl LZ4FDecompressionOptions { pub fn stable(stable: bool) -> Self { Self { stable_dst: u32::from(stable), ..Default::default() } } } extern "C" { pub fn LZ4F_getVersion() -> c_uint; pub fn LZ4F_compressBound(src_size: size_t, prefs: *const Preferences) -> size_t; pub fn LZ4F_compressFrameBound(src_size: size_t, prefs: *const Preferences) -> size_t; pub fn LZ4F_compressFrame( dst_buffer: *mut c_void, dst_capacity: size_t, src_buffer: *const c_void, src_size: size_t, prefs: *const Preferences, ) -> size_t; pub fn LZ4F_decompress_usingDict( ctx: *mut LZ4FDecompressionCtx, dst_buffer: *mut c_void, dst_size_ptr: *mut size_t, src_buffer: *const c_void, src_size_ptr: *mut size_t, dict: *const c_void, dict_size: size_t, opt: *const LZ4FDecompressionOptions, ) -> size_t; pub fn LZ4F_createCDict( dict_buffer: *const c_void, dict_size: size_t, ) -> *mut LZ4FCompressionDict; pub fn LZ4F_freeCDict(dict: *mut LZ4FCompressionDict); pub fn LZ4F_createCompressionContext( ctx: *mut *mut LZ4FCompressionCtx, version: c_uint, ) -> size_t; pub fn LZ4F_freeCompressionContext(ctx: *mut LZ4FCompressionCtx); pub fn LZ4F_compressBegin( ctx: *mut LZ4FCompressionCtx, dst_buffer: *mut c_void, dst_capacity: size_t, prefs: *const Preferences, ) -> size_t; pub fn LZ4F_compressBegin_usingCDict( ctx: *mut LZ4FCompressionCtx, dst_buffer: *mut c_void, dst_capacity: size_t, dist: *const LZ4FCompressionDict, prefs: *const Preferences, ) -> size_t; pub fn LZ4F_compressUpdate( ctx: *mut LZ4FCompressionCtx, dst_buffer: *mut c_void, dst_capacity: size_t, src_buffer: *const c_void, src_size: size_t, opt: *const LZ4FCompressionOptions, ) -> size_t; pub fn LZ4F_flush( ctx: *mut LZ4FCompressionCtx, dst_buffer: *mut c_void, dst_capacity: size_t, opt: *const LZ4FCompressionOptions, ) -> size_t; pub fn LZ4F_compressEnd( ctx: *mut LZ4FCompressionCtx, dst_buffer: *mut c_void, dst_capacity: size_t, opt: *const LZ4FCompressionOptions, ) -> size_t; pub fn LZ4F_createDecompressionContext( ctx: *mut *mut LZ4FDecompressionCtx, version: c_uint, ) -> size_t; pub fn LZ4F_freeDecompressionContext(ctx: *mut LZ4FDecompressionCtx) -> size_t; pub fn LZ4F_resetDecompressionContext(ctx: *mut LZ4FDecompressionCtx); pub fn LZ4F_headerSize(src: *const c_void, src_size: size_t) -> size_t; pub fn LZ4F_getFrameInfo( ctx: *mut LZ4FDecompressionCtx, frame_info_ptr: *mut FrameInfo, src_buffer: *const c_void, src_size_ptr: *mut size_t, ) -> size_t; } lzzzz-2.0.0/src/lz4f/dictionary.rs000064400000000000000000000012411046102023000152050ustar 00000000000000use super::{api::DictionaryHandle, Result}; use std::sync::Arc; /// A pre-compiled dictionary for the efficient compression. #[derive(Clone)] pub struct Dictionary(Arc); impl Dictionary { /// Builds a new `Dictionary`. pub fn new(data: &[u8]) -> Result { Ok(Self(Arc::new(DictionaryHandle::new(data)?))) } pub(crate) fn handle(&self) -> &DictionaryHandle { &self.0 } } #[cfg(test)] mod tests { use super::Dictionary; #[test] fn create_dictionary() { assert!(Dictionary::new(&[]).is_ok()); assert!(Dictionary::new(&b"quick brown fox jumps over the lazy dog"[..]).is_ok()); } } lzzzz-2.0.0/src/lz4f/error.rs000064400000000000000000000035531046102023000142010ustar 00000000000000use std::{convert, fmt, io}; /// A list specifying general categories of LZ4F error. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] #[allow(missing_docs)] pub enum ErrorKind { Generic, MaxBlockSizeInvalid, BlockModeInvalid, ContentChecksumFlagInvalid, CompressionLevelInvalid, HeaderVersionWrong, BlockChecksumInvalid, ReservedFlagSet, AllocationFailed, SrcSizeTooLarge, DstMaxSizeTooSmall, FrameHeaderIncomplete, FrameTypeUnknown, FrameSizeWrong, SrcPtrWrong, DecompressionFailed, HeaderChecksumInvalid, ContentChecksumInvalid, FrameDecodingAlreadyStarted, } impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { ::fmt(self, f) } } /// The error type for LZ4F operations. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Error { Lz4f(ErrorKind), Common(crate::ErrorKind), } impl Error { pub(super) const fn new(kind: ErrorKind) -> Self { Self::Lz4f(kind) } } impl convert::From for io::Error { fn from(err: Error) -> Self { Self::new(io::ErrorKind::Other, err) } } impl convert::From for Error { fn from(err: crate::Error) -> Self { Self::Common(err.kind()) } } impl fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { match self { Self::Lz4f(kind) => ::fmt(kind, f), Self::Common(kind) => ::fmt(kind, f), } } } impl std::error::Error for Error {} /// A specialized [`Result`] type for LZ4F operations. /// /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html pub type Result = std::result::Result; lzzzz-2.0.0/src/lz4f/frame.rs000064400000000000000000000125771046102023000141500ustar 00000000000000//! LZ4 Frame Compressor/Decompressor use super::{api, Result}; use crate::{common::DEFAULT_BUF_SIZE, lz4f::Preferences, Error, ErrorKind}; use std::{cell::RefCell, ops::Deref}; /// Calculates the maximum size of the compressed output. /// /// If `original_size` is too large to compress, this returns `0`. /// /// Returned values are reliable only for [`compress`] and [`compress_to_vec`]. /// Streaming compressors may produce larger compressed frames. /// /// [`compress`]: fn.compress.html /// [`compress_to_vec`]: fn.compress_to_vec.html #[must_use] pub fn max_compressed_size(original_size: usize, prefs: &Preferences) -> usize { api::compress_frame_bound(original_size, prefs) } /// Performs LZ4F compression. /// /// Ensure that the destination slice has enough capacity. /// If `dst.len()` is smaller than `lz4f::max_compressed_size(src.len())`, /// this function may fail. /// /// Returns the number of bytes written into the destination buffer. /// /// # Example /// /// Compress data with the default compression mode: /// ``` /// use lzzzz::lz4f; /// /// let prefs = lz4f::Preferences::default(); /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = [0u8; 2048]; /// /// // The slice should have enough capacity. /// assert!(buf.len() >= lz4f::max_compressed_size(data.len(), &prefs)); /// /// let len = lz4f::compress(data, &mut buf, &prefs)?; /// let compressed = &buf[..len]; /// # let mut buf = Vec::new(); /// # lz4f::decompress_to_vec(compressed, &mut buf)?; /// # assert_eq!(buf.as_slice(), &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress(src: &[u8], dst: &mut [u8], prefs: &Preferences) -> Result { compress_to_ptr(src, dst.as_mut_ptr(), dst.len(), prefs) } fn compress_to_ptr(src: &[u8], dst: *mut u8, dst_len: usize, prefs: &Preferences) -> Result { let mut prefs = *prefs; if prefs.frame_info().content_size() > 0 { prefs.set_content_size(src.len()); } api::compress(src, dst, dst_len, &prefs) } /// Appends a compressed frame to `Vec`. /// /// Returns the number of bytes appended to the given `Vec`. /// /// # Example /// /// Compress data with the default compression mode: /// ``` /// use lzzzz::lz4f; /// /// let prefs = lz4f::Preferences::default(); /// let data = b"The quick brown fox jumps over the lazy dog."; /// let mut buf = Vec::new(); /// /// let len = lz4f::compress_to_vec(data, &mut buf, &prefs)?; /// let compressed = &buf; /// # let mut buf = Vec::new(); /// # lz4f::decompress_to_vec(compressed, &mut buf)?; /// # assert_eq!(buf.as_slice(), &data[..]); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn compress_to_vec(src: &[u8], dst: &mut Vec, prefs: &Preferences) -> Result { let orig_len = dst.len(); dst.reserve(max_compressed_size(src.len(), prefs)); #[allow(unsafe_code)] unsafe { let result = compress_to_ptr( src, dst.as_mut_ptr().add(orig_len), dst.capacity() - orig_len, prefs, ); dst.set_len(orig_len + result.as_ref().unwrap_or(&0)); result } } /// Decompresses an LZ4 frame. /// /// Returns the number of bytes appended to the given `Vec`. /// /// # Example /// /// ``` /// use lzzzz::lz4f; /// /// const COMPRESSED_DATA: &str = /// "BCJNGGBAgiwAAIBUaGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLgAAAAA="; /// /// let data = base64::decode(COMPRESSED_DATA).unwrap(); /// let mut buf = Vec::new(); /// /// lz4f::decompress_to_vec(&data[..], &mut buf)?; /// /// assert_eq!( /// &buf[..], /// &b"The quick brown fox jumps over the lazy dog."[..] /// ); /// # Ok::<(), std::io::Error>(()) /// ``` pub fn decompress_to_vec(src: &[u8], dst: &mut Vec) -> Result { let header_len = dst.len(); let mut src_offset = 0; let mut dst_offset = header_len; DecompressionCtx::with(|ctx| { let mut ctx = ctx.borrow_mut(); ctx.reset(); loop { dst.resize_with(dst.len() + DEFAULT_BUF_SIZE, Default::default); match ctx.decompress_dict(&src[src_offset..], &mut dst[dst_offset..], &[], false) { Ok((src_len, dst_len, expected)) => { src_offset += src_len; dst_offset += dst_len; if expected == 0 { dst.resize_with(dst_offset, Default::default); return Ok(dst_offset - header_len); } else if src_offset >= src.len() { dst.resize_with(header_len, Default::default); return Err(Error::new(ErrorKind::CompressedDataIncomplete).into()); } } Err(err) => { dst.resize_with(header_len, Default::default); return Err(err); } } } }) } struct DecompressionCtx(RefCell); impl DecompressionCtx { fn new() -> Self { Self(RefCell::new(api::DecompressionContext::new().unwrap())) } fn with(f: F) -> R where F: FnOnce(&RefCell) -> R, { DECOMPRESSION_CTX.with(|state| (f)(state)) } } impl Deref for DecompressionCtx { type Target = RefCell; fn deref(&self) -> &Self::Target { &self.0 } } thread_local!(static DECOMPRESSION_CTX: DecompressionCtx = DecompressionCtx::new()); lzzzz-2.0.0/src/lz4f/frame_info.rs000064400000000000000000000061031046102023000151470ustar 00000000000000use std::os::raw::{c_uint, c_ulonglong}; /// Block size flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] #[repr(C)] pub enum BlockSize { Default = 0, Max64KB = 4, Max256KB = 5, Max1MB = 6, Max4MB = 7, } impl Default for BlockSize { fn default() -> Self { Self::Default } } /// Block mode flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum BlockMode { Linked, Independent, } impl Default for BlockMode { fn default() -> Self { Self::Linked } } /// Content checksum flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum ContentChecksum { Disabled, Enabled, } impl Default for ContentChecksum { fn default() -> Self { Self::Disabled } } /// Frame type flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum FrameType { Frame, SkippableFrame, } impl Default for FrameType { fn default() -> Self { Self::Frame } } /// Block checksum flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum BlockChecksum { Disabled, Enabled, } impl Default for BlockChecksum { fn default() -> Self { Self::Disabled } } /// LZ4 Frame parameters. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub struct FrameInfo { block_size: BlockSize, block_mode: BlockMode, content_checksum: ContentChecksum, frame_type: FrameType, content_size: c_ulonglong, dict_id: c_uint, block_checksum: BlockChecksum, } impl FrameInfo { /// Returns the block size. pub const fn block_size(&self) -> BlockSize { self.block_size } /// Returns the block mode. pub const fn block_mode(&self) -> BlockMode { self.block_mode } /// Returns the content checksum. pub const fn content_checksum(&self) -> ContentChecksum { self.content_checksum } /// Returns the frame type. pub const fn frame_type(&self) -> FrameType { self.frame_type } /// Returns the content size. pub const fn content_size(&self) -> usize { self.content_size as usize } /// Returns the dictionary id. pub const fn dict_id(&self) -> u32 { self.dict_id } /// Returns the block checksum. pub const fn block_checksum(&self) -> BlockChecksum { self.block_checksum } pub(super) fn set_block_size(&mut self, block_size: BlockSize) { self.block_size = block_size; } pub(super) fn set_block_mode(&mut self, block_mode: BlockMode) { self.block_mode = block_mode; } pub(super) fn set_content_checksum(&mut self, checksum: ContentChecksum) { self.content_checksum = checksum; } pub(super) fn set_content_size(&mut self, size: usize) { self.content_size = size as c_ulonglong; } pub(super) fn set_dict_id(&mut self, dict_id: u32) { self.dict_id = dict_id as c_uint; } pub(super) fn set_block_checksum(&mut self, checksum: BlockChecksum) { self.block_checksum = checksum; } } lzzzz-2.0.0/src/lz4f/mod.rs000064400000000000000000000005021046102023000136160ustar 00000000000000//! LZ4F compression and decompression. //! //! LZ4F: LZ4 Frame Format. mod api; mod binding; mod dictionary; mod error; mod frame; mod frame_info; mod preferences; mod stream; pub use dictionary::*; pub use error::*; pub use frame::*; pub use frame_info::*; pub use preferences::*; pub use stream::{comp::*, decomp::*}; lzzzz-2.0.0/src/lz4f/preferences.rs000064400000000000000000000213171046102023000153470ustar 00000000000000use super::frame_info::{BlockChecksum, BlockMode, BlockSize, ContentChecksum, FrameInfo}; use std::os::raw::{c_int, c_uint}; /// Predefined compression level (0). pub const CLEVEL_DEFAULT: i32 = 0; /// Predefined compression level (10). pub const CLEVEL_HIGH: i32 = 10; /// Predefined compression level (12). pub const CLEVEL_MAX: i32 = 12; /// Auto flush mode flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum AutoFlush { Disabled, Enabled, } impl Default for AutoFlush { fn default() -> Self { Self::Disabled } } /// Decompression speed mode flag. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub enum FavorDecSpeed { Disabled, Enabled, } impl Default for FavorDecSpeed { fn default() -> Self { Self::Disabled } } /// Compression preferences. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[repr(C)] pub struct Preferences { frame_info: FrameInfo, compression_level: c_int, auto_flush: AutoFlush, favor_dec_speed: FavorDecSpeed, _reserved: [c_uint; 3], } impl Preferences { /// Returns the frame info. pub const fn frame_info(&self) -> FrameInfo { self.frame_info } /// Returns the compression level. pub const fn compression_level(&self) -> i32 { self.compression_level } /// Returns the auto flush mode flag. pub const fn auto_flush(&self) -> AutoFlush { self.auto_flush } /// Returns the decompression speed mode flag. pub const fn favor_dec_speed(&self) -> FavorDecSpeed { self.favor_dec_speed } pub(super) fn set_block_size(&mut self, block_size: BlockSize) { self.frame_info.set_block_size(block_size); } pub(super) fn set_block_mode(&mut self, block_mode: BlockMode) { self.frame_info.set_block_mode(block_mode); } pub(super) fn set_content_checksum(&mut self, checksum: ContentChecksum) { self.frame_info.set_content_checksum(checksum); } pub(super) fn set_content_size(&mut self, size: usize) { self.frame_info.set_content_size(size); } pub(super) fn set_dict_id(&mut self, dict_id: u32) { self.frame_info.set_dict_id(dict_id); } pub(super) fn set_block_checksum(&mut self, checksum: BlockChecksum) { self.frame_info.set_block_checksum(checksum); } pub(super) fn set_compression_level(&mut self, level: i32) { self.compression_level = level as c_int; } pub(super) fn set_favor_dec_speed(&mut self, dec_speed: FavorDecSpeed) { self.favor_dec_speed = dec_speed; } pub(super) fn set_auto_flush(&mut self, auto_flush: AutoFlush) { self.auto_flush = auto_flush; } } /// Builds a custom `Preferences`. /// /// # Example /// /// ``` /// use lzzzz::lz4f::{BlockSize, PreferencesBuilder, CLEVEL_MAX}; /// /// let pref = PreferencesBuilder::new() /// .block_size(BlockSize::Max1MB) /// .compression_level(CLEVEL_MAX) /// .build(); /// ``` #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PreferencesBuilder { prefs: Preferences, } impl PreferencesBuilder { /// Creates a new `PreferencesBuilder`. pub fn new() -> Self { Default::default() } /// Sets the block size. pub fn block_size(&mut self, block_size: BlockSize) -> &mut Self { self.prefs.set_block_size(block_size); self } /// Sets the block mode. pub fn block_mode(&mut self, block_mode: BlockMode) -> &mut Self { self.prefs.set_block_mode(block_mode); self } /// Sets the content checksum. pub fn content_checksum(&mut self, checksum: ContentChecksum) -> &mut Self { self.prefs.set_content_checksum(checksum); self } /// Sets the content size. /// /// A value greater than 0 enables the content size field in the frame header and /// automatically replaced with an actual content size. pub fn content_size(&mut self, size: usize) -> &mut Self { self.prefs.set_content_size(size); self } /// Sets the dictionary id. pub fn dict_id(&mut self, dict_id: u32) -> &mut Self { self.prefs.set_dict_id(dict_id); self } /// Sets the block checksum. pub fn block_checksum(&mut self, checksum: BlockChecksum) -> &mut Self { self.prefs.set_block_checksum(checksum); self } /// Sets the compression level. pub fn compression_level(&mut self, level: i32) -> &mut Self { self.prefs.set_compression_level(level); self } /// Sets the decompression speed mode flag. pub fn favor_dec_speed(&mut self, dec_speed: FavorDecSpeed) -> &mut Self { self.prefs.set_favor_dec_speed(dec_speed); self } /// Sets the auto flush mode flag. pub fn auto_flush(&mut self, auto_flush: AutoFlush) -> &mut Self { self.prefs.set_auto_flush(auto_flush); self } /// Builds a `Preferences` with this configuration. pub const fn build(&self) -> Preferences { self.prefs } } impl From for PreferencesBuilder { fn from(prefs: Preferences) -> Self { Self { prefs } } } #[cfg(test)] mod tests { use crate::lz4f::{ BlockChecksum, BlockMode, BlockSize, ContentChecksum, FavorDecSpeed, Preferences, PreferencesBuilder, CLEVEL_DEFAULT, CLEVEL_HIGH, CLEVEL_MAX, }; use std::{i32, u32}; #[test] fn preferences_builder() { assert_eq!(PreferencesBuilder::new().build(), Preferences::default()); assert_eq!( PreferencesBuilder::new() .favor_dec_speed(FavorDecSpeed::Enabled) .build() .favor_dec_speed, FavorDecSpeed::Enabled ); assert_eq!( PreferencesBuilder::new() .block_size(BlockSize::Max64KB) .build() .frame_info .block_size(), BlockSize::Max64KB ); assert_eq!( PreferencesBuilder::new() .block_size(BlockSize::Max256KB) .build() .frame_info .block_size(), BlockSize::Max256KB ); assert_eq!( PreferencesBuilder::new() .block_size(BlockSize::Max1MB) .build() .frame_info .block_size(), BlockSize::Max1MB ); assert_eq!( PreferencesBuilder::new() .block_size(BlockSize::Max4MB) .build() .frame_info .block_size(), BlockSize::Max4MB ); assert_eq!( PreferencesBuilder::new() .content_checksum(ContentChecksum::Enabled) .build() .frame_info .content_checksum(), ContentChecksum::Enabled ); assert_eq!( PreferencesBuilder::new() .block_mode(BlockMode::Independent) .build() .frame_info .block_mode(), BlockMode::Independent ); assert_eq!( PreferencesBuilder::new() .compression_level(i32::MAX) .build() .compression_level, i32::MAX ); assert_eq!( PreferencesBuilder::new() .compression_level(CLEVEL_DEFAULT) .build() .compression_level, CLEVEL_DEFAULT ); assert_eq!( PreferencesBuilder::new() .compression_level(CLEVEL_HIGH) .build() .compression_level, CLEVEL_HIGH ); assert_eq!( PreferencesBuilder::new() .compression_level(CLEVEL_MAX) .build() .compression_level, CLEVEL_MAX ); assert_eq!( PreferencesBuilder::new() .compression_level(i32::MIN) .build() .compression_level, i32::MIN ); assert_eq!( PreferencesBuilder::new() .block_checksum(BlockChecksum::Enabled) .build() .frame_info .block_checksum(), BlockChecksum::Enabled ); assert_eq!( PreferencesBuilder::new() .dict_id(u32::MAX) .build() .frame_info .dict_id(), u32::MAX ); assert_eq!( PreferencesBuilder::new() .dict_id(u32::MIN) .build() .frame_info .dict_id(), u32::MIN ); } } lzzzz-2.0.0/src/lz4f/stream/comp/bufread.rs000064400000000000000000000070501046102023000167050ustar 00000000000000use super::{Compressor, Dictionary, Preferences}; use crate::lz4f::Result; use std::{ fmt, io::{BufRead, Read}, }; /// The [`BufRead`]-based streaming compressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// # /// # tmp_dir.child("foo.txt").write_str("Hello").unwrap(); /// # /// use lzzzz::lz4f::BufReadCompressor; /// use std::{ /// fs::File, /// io::{prelude::*, BufReader}, /// }; /// /// let mut f = File::open("foo.txt")?; /// let mut b = BufReader::new(f); /// let mut r = BufReadCompressor::new(&mut b, Default::default())?; /// /// let mut buf = Vec::new(); /// r.read_to_end(&mut buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html pub struct BufReadCompressor { pub(super) inner: R, pub(super) comp: Compressor, consumed: usize, } impl BufReadCompressor { /// Creates a new `BufReadCompressor`. pub fn new(reader: R, prefs: Preferences) -> Result { Ok(Self { inner: reader, comp: Compressor::new(prefs, None)?, consumed: 0, }) } /// Creates a new `BufReadCompressor` with a dictionary. pub fn with_dict(reader: R, prefs: Preferences, dict: Dictionary) -> Result { Ok(Self { inner: reader, comp: Compressor::new(prefs, Some(dict))?, consumed: 0, }) } /// Returns ownership of the reader. pub fn into_inner(self) -> R { self.inner } /// Returns a mutable reference to the reader. pub fn get_mut(&mut self) -> &mut R { &mut self.inner } /// Returns a shared reference to the reader. pub fn get_ref(&self) -> &R { &self.inner } } impl fmt::Debug for BufReadCompressor where R: BufRead + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("BufReadCompressor") .field("reader", &self.inner) .field("prefs", &self.comp.prefs()) .finish() } } impl Read for BufReadCompressor { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let consumed = { let inner_buf = self.inner.fill_buf()?; if inner_buf.is_empty() { self.comp.end(false)?; if self.comp.buf().is_empty() { return Ok(0); } 0 } else { self.comp.update(inner_buf, false)?; inner_buf.len() } }; self.inner.consume(consumed); let len = std::cmp::min(buf.len(), self.comp.buf().len() - self.consumed); buf[..len].copy_from_slice(&self.comp.buf()[self.consumed..][..len]); self.consumed += len; if self.consumed >= self.comp.buf().len() { self.comp.clear_buf(); self.consumed = 0; } Ok(len) } } impl BufRead for BufReadCompressor { fn fill_buf(&mut self) -> std::io::Result<&[u8]> { let _ = self.read(&mut [])?; Ok(&self.comp.buf()[self.consumed..]) } fn consume(&mut self, amt: usize) { self.consumed += amt; if self.consumed >= self.comp.buf().len() { self.comp.clear_buf(); self.consumed = 0; } } } lzzzz-2.0.0/src/lz4f/stream/comp/mod.rs000064400000000000000000000064361046102023000160630ustar 00000000000000//! Streaming LZ4F compressors. mod bufread; mod read; mod write; use crate::lz4f::Result; pub use bufread::*; pub use read::*; pub use write::*; use crate::lz4f::{ api::{CompressionContext, LZ4F_HEADER_SIZE_MAX}, Dictionary, Preferences, }; pub(crate) struct Compressor { ctx: CompressionContext, prefs: Preferences, state: State, buffer: Vec, } impl Compressor { pub fn new(prefs: Preferences, dict: Option) -> Result { Ok(Self { ctx: CompressionContext::new(dict)?, prefs, state: State::Created, buffer: Vec::with_capacity(LZ4F_HEADER_SIZE_MAX), }) } pub fn prefs(&self) -> &Preferences { &self.prefs } fn begin(&mut self) -> Result<()> { if let State::Created = self.state { assert!(self.buffer.is_empty()); self.state = State::Active; let len = self.ctx.begin( self.buffer.as_mut_ptr(), self.buffer.capacity(), &self.prefs, )?; #[allow(unsafe_code)] unsafe { self.buffer.set_len(len); } } Ok(()) } pub fn update(&mut self, src: &[u8], stable_src: bool) -> Result<()> { self.begin()?; let ext_len = CompressionContext::compress_bound(src.len(), &self.prefs); self.buffer.reserve(ext_len); let offset = self.buffer.len(); #[allow(unsafe_code)] unsafe { let len = self.ctx.update( self.buffer.as_mut_ptr().add(offset), self.buffer.capacity() - offset, src, stable_src, )?; self.buffer.set_len(offset + len); if len == 0 { self.flush(stable_src) } else { Ok(()) } } } pub fn flush(&mut self, stable_src: bool) -> Result<()> { self.begin()?; let ext_len = CompressionContext::compress_bound(0, &self.prefs); self.buffer.reserve(ext_len); let offset = self.buffer.len(); #[allow(unsafe_code)] unsafe { let len = self.ctx.flush( self.buffer.as_mut_ptr().add(offset), self.buffer.capacity() - offset, stable_src, )?; self.buffer.set_len(offset + len); } Ok(()) } pub fn end(&mut self, stable_src: bool) -> Result<()> { self.begin()?; if let State::Active = self.state { self.state = State::Finished; let ext_len = CompressionContext::compress_bound(0, &self.prefs); self.buffer.reserve(ext_len); let offset = self.buffer.len(); #[allow(unsafe_code)] unsafe { let len = self.ctx.end( self.buffer.as_mut_ptr().add(offset), self.buffer.capacity() - offset, stable_src, )?; self.buffer.set_len(offset + len); } } Ok(()) } pub fn buf(&self) -> &[u8] { &self.buffer } pub fn clear_buf(&mut self) { self.buffer.clear(); } } pub(crate) enum State { Created, Active, Finished, } lzzzz-2.0.0/src/lz4f/stream/comp/read.rs000064400000000000000000000044731046102023000162160ustar 00000000000000use super::{BufReadCompressor, Dictionary, Preferences}; use crate::lz4f::Result; use std::{ fmt, io::{BufReader, Read}, }; /// The [`Read`]-based streaming compressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// # /// # tmp_dir.child("foo.txt").write_str("Hello").unwrap(); /// # /// use lzzzz::lz4f::ReadCompressor; /// use std::{fs::File, io::prelude::*}; /// /// let mut f = File::open("foo.txt")?; /// let mut r = ReadCompressor::new(&mut f, Default::default())?; /// /// let mut buf = Vec::new(); /// r.read_to_end(&mut buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html pub struct ReadCompressor { inner: BufReadCompressor>, } impl ReadCompressor { /// Creates a new `ReadCompressor`. pub fn new(reader: R, prefs: Preferences) -> Result { Ok(Self { inner: BufReadCompressor::new(BufReader::new(reader), prefs)?, }) } /// Creates a new `ReadCompressor` with a dictionary. pub fn with_dict(reader: R, prefs: Preferences, dict: Dictionary) -> Result { Ok(Self { inner: BufReadCompressor::with_dict(BufReader::new(reader), prefs, dict)?, }) } /// Returns ownership of the reader. pub fn into_inner(self) -> R { self.inner.into_inner().into_inner() } /// Returns a mutable reference to the reader. pub fn get_mut(&mut self) -> &mut R { self.inner.get_mut().get_mut() } /// Returns a shared reference to the reader. pub fn get_ref(&self) -> &R { self.inner.get_ref().get_ref() } } impl fmt::Debug for ReadCompressor where R: Read + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("ReadCompressor") .field("reader", &self.inner.inner.get_ref()) .field("prefs", &self.inner.comp.prefs()) .finish() } } impl Read for ReadCompressor { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.inner.read(buf) } } lzzzz-2.0.0/src/lz4f/stream/comp/write.rs000064400000000000000000000057371046102023000164410ustar 00000000000000use super::{Compressor, Dictionary, Preferences}; use crate::lz4f::Result; use std::{fmt, io::Write}; /// The [`Write`]-based streaming compressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// use lzzzz::lz4f::WriteCompressor; /// use std::{fs::File, io::prelude::*}; /// /// let mut f = File::create("foo.lz4")?; /// let mut w = WriteCompressor::new(&mut f, Default::default())?; /// /// w.write_all(b"Hello world!")?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html pub struct WriteCompressor { inner: Option, comp: Compressor, } impl WriteCompressor { /// Creates a new `WriteCompressor`. pub fn new(writer: W, prefs: Preferences) -> Result { Ok(Self { inner: Some(writer), comp: Compressor::new(prefs, None)?, }) } /// Creates a new `WriteCompressor` with a dictionary. pub fn with_dict(writer: W, prefs: Preferences, dict: Dictionary) -> Result { Ok(Self { inner: Some(writer), comp: Compressor::new(prefs, Some(dict))?, }) } /// Returns a mutable reference to the writer. pub fn get_mut(&mut self) -> &mut W { self.inner.as_mut().unwrap() } /// Returns a shared reference to the writer. pub fn get_ref(&self) -> &W { self.inner.as_ref().unwrap() } /// Returns the ownership of the writer, finishing the stream in the process. pub fn into_inner(mut self) -> W { let _ = self.end(); self.inner.take().unwrap() } fn end(&mut self) -> std::io::Result<()> { if let Some(device) = &mut self.inner { self.comp.end(false)?; device.write_all(self.comp.buf())?; self.comp.clear_buf(); device.flush()?; } Ok(()) } } impl fmt::Debug for WriteCompressor where W: Write + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("WriteCompressor") .field("writer", &self.inner) .field("prefs", &self.comp.prefs()) .finish() } } impl Write for WriteCompressor { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.comp.update(buf, false)?; self.inner.as_mut().unwrap().write_all(self.comp.buf())?; self.comp.clear_buf(); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { self.comp.flush(false)?; self.inner.as_mut().unwrap().write_all(self.comp.buf())?; self.comp.clear_buf(); self.inner.as_mut().unwrap().flush() } } impl Drop for WriteCompressor { fn drop(&mut self) { let _ = self.end(); } } lzzzz-2.0.0/src/lz4f/stream/decomp/bufread.rs000064400000000000000000000075101046102023000172170ustar 00000000000000use super::Decompressor; use crate::lz4f::{FrameInfo, Result}; use std::{ borrow::Cow, fmt, io::{BufRead, Read}, }; /// The [`BufRead`]-based streaming decompressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// # /// # let mut buf = Vec::new(); /// # lzzzz::lz4f::compress_to_vec(b"Hello world!", &mut buf, &Default::default())?; /// # tmp_dir.child("foo.lz4").write_binary(&buf).unwrap(); /// # /// use lzzzz::lz4f::BufReadDecompressor; /// use std::{ /// fs::File, /// io::{prelude::*, BufReader}, /// }; /// /// let mut f = File::open("foo.lz4")?; /// let mut b = BufReader::new(f); /// let mut r = BufReadDecompressor::new(&mut b)?; /// /// let mut buf = Vec::new(); /// r.read_to_end(&mut buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html pub struct BufReadDecompressor<'a, R: BufRead> { pub(super) inner: R, decomp: Decompressor<'a>, consumed: usize, } impl<'a, R: BufRead> BufReadDecompressor<'a, R> { /// Creates a new `BufReadDecompressor`. pub fn new(reader: R) -> Result { Ok(Self { inner: reader, decomp: Decompressor::new()?, consumed: 0, }) } /// Sets the dictionary. pub fn set_dict(&mut self, dict: D) where D: Into>, { self.decomp.set_dict(dict); } /// Reads the frame header and returns `FrameInfo`. /// /// Calling this function before any `Read` or `BufRead` operations /// does not consume the frame body. pub fn read_frame_info(&mut self) -> std::io::Result { loop { if let Some(frame) = self.decomp.frame_info() { return Ok(frame); } self.decomp.decode_header_only(true); let _ = self.read(&mut [])?; self.decomp.decode_header_only(false); } } /// Returns ownership of the reader. pub fn into_inner(self) -> R { self.inner } /// Returns a mutable reference to the reader. pub fn get_mut(&mut self) -> &mut R { &mut self.inner } /// Returns a shared reference to the reader. pub fn get_ref(&self) -> &R { &self.inner } } impl fmt::Debug for BufReadDecompressor<'_, R> where R: BufRead + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("BufReadDecompressor") .field("reader", &self.inner) .finish() } } impl Read for BufReadDecompressor<'_, R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { loop { let inner_buf = self.inner.fill_buf()?; let consumed = self.decomp.decompress(inner_buf)?; self.inner.consume(consumed); if consumed == 0 { break; } } let len = std::cmp::min(buf.len(), self.decomp.buf().len() - self.consumed); buf[..len].copy_from_slice(&self.decomp.buf()[self.consumed..][..len]); self.consumed += len; if self.consumed >= self.decomp.buf().len() { self.decomp.clear_buf(); self.consumed = 0; } Ok(len) } } impl BufRead for BufReadDecompressor<'_, R> { fn fill_buf(&mut self) -> std::io::Result<&[u8]> { let _ = self.read(&mut [])?; Ok(&self.decomp.buf()[self.consumed..]) } fn consume(&mut self, amt: usize) { self.consumed += amt; if self.consumed >= self.decomp.buf().len() { self.decomp.clear_buf(); self.consumed = 0; } } } lzzzz-2.0.0/src/lz4f/stream/decomp/mod.rs000064400000000000000000000113771046102023000163740ustar 00000000000000//! Streaming LZ4F decompressors. mod bufread; mod read; mod write; pub use bufread::*; pub use read::*; pub use write::*; use crate::{ common::DEFAULT_BUF_SIZE, lz4f::{ api::{ header_size, DecompressionContext, LZ4F_HEADER_SIZE_MAX, LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, }, FrameInfo, Result, }, Error, ErrorKind, }; use std::{borrow::Cow, cmp, pin::Pin, ptr}; #[derive(Clone, Copy, PartialEq)] struct DictPtr(*const u8, usize); #[allow(unsafe_code)] unsafe impl Send for DictPtr {} enum State { Header { header: [u8; LZ4F_HEADER_SIZE_MAX], header_len: usize, }, Body { frame_info: FrameInfo, comp_dict: Option, }, } pub(crate) struct Decompressor<'a> { ctx: DecompressionContext, state: State, buffer: Vec, dict: Pin>, header_only: bool, } impl<'a> Decompressor<'a> { pub fn new() -> Result { Ok(Self { ctx: DecompressionContext::new()?, state: State::Header { header: [0; LZ4F_HEADER_SIZE_MAX], header_len: 0, }, buffer: Vec::new(), dict: Pin::new(Cow::Borrowed(&[])), header_only: false, }) } pub fn set_dict(&mut self, dict: D) where D: Into>, { self.dict = Pin::new(dict.into()); } pub fn frame_info(&self) -> Option { if let State::Body { frame_info, .. } = self.state { Some(frame_info) } else { None } } pub fn decode_header_only(&mut self, flag: bool) { self.header_only = flag; } pub fn decompress(&mut self, src: &[u8]) -> Result { let mut header_consumed = 0; if let State::Header { ref mut header, ref mut header_len, } = &mut self.state { if *header_len < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH { let len = cmp::min(LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH - *header_len, src.len()); header[*header_len..*header_len + len].copy_from_slice(&src[..len]); *header_len += len; header_consumed += len; } if *header_len >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH { let exact_header_len = header_size(&header[..*header_len]); if exact_header_len > LZ4F_HEADER_SIZE_MAX { return Err(Error::new(ErrorKind::FrameHeaderInvalid).into()); } let src = &src[header_consumed..]; if *header_len < exact_header_len { let len = cmp::min(exact_header_len - *header_len, src.len()); header[*header_len..*header_len + len].copy_from_slice(&src[..len]); *header_len += len; header_consumed += len; } if *header_len >= exact_header_len { let (frame, rep) = self.ctx.get_frame_info(&header[..*header_len])?; header_consumed = cmp::min(header_consumed, rep); self.state = State::Body { frame_info: frame, comp_dict: None, } } } } if let State::Header { header, header_len } = self.state { if src.is_empty() { self.ctx.get_frame_info(&header[..header_len])?; } } if self.header_only { return Ok(header_consumed); } let src = &src[header_consumed..]; let dict_ptr = self.dict_ptr(); if let State::Body { ref mut comp_dict, .. } = &mut self.state { if dict_ptr != *comp_dict.get_or_insert(dict_ptr) { return Err(Error::new(ErrorKind::DictionaryChangedDuringDecompression).into()); } let len = self.buffer.len(); if len < DEFAULT_BUF_SIZE { self.buffer.resize_with(DEFAULT_BUF_SIZE, Default::default) } let (src_len, dst_len, _) = self.ctx .decompress_dict(src, &mut self.buffer[len..], &self.dict, false)?; self.buffer.resize_with(len + dst_len, Default::default); Ok(src_len + header_consumed) } else { Ok(header_consumed) } } fn dict_ptr(&self) -> DictPtr { let dict = &self.dict; if dict.is_empty() { DictPtr(ptr::null(), 0) } else { DictPtr(dict.as_ptr(), dict.len()) } } pub fn buf(&self) -> &[u8] { &self.buffer } pub fn clear_buf(&mut self) { self.buffer.clear(); } } lzzzz-2.0.0/src/lz4f/stream/decomp/read.rs000064400000000000000000000050461046102023000165240ustar 00000000000000use super::BufReadDecompressor; use crate::lz4f::{FrameInfo, Result}; use std::{ borrow::Cow, fmt, io::{BufReader, Read}, }; /// The [`Read`]-based streaming decompressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// # /// # let mut buf = Vec::new(); /// # lzzzz::lz4f::compress_to_vec(b"Hello world!", &mut buf, &Default::default())?; /// # tmp_dir.child("foo.lz4").write_binary(&buf).unwrap(); /// # /// use lzzzz::lz4f::ReadDecompressor; /// use std::{fs::File, io::prelude::*}; /// /// let mut f = File::open("foo.lz4")?; /// let mut r = ReadDecompressor::new(&mut f)?; /// /// let mut buf = Vec::new(); /// r.read_to_end(&mut buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html pub struct ReadDecompressor<'a, R: Read> { inner: BufReadDecompressor<'a, BufReader>, } impl fmt::Debug for ReadDecompressor<'_, R> where R: Read + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("ReadDecompressor") .field("reader", &self.inner.inner.get_ref()) .finish() } } impl<'a, R: Read> ReadDecompressor<'a, R> { /// Creates a new `ReadDecompressor`. pub fn new(reader: R) -> Result { Ok(Self { inner: BufReadDecompressor::new(BufReader::new(reader))?, }) } /// Sets the dictionary. pub fn set_dict(&mut self, dict: D) where D: Into>, { self.inner.set_dict(dict); } /// Reads the frame header and returns `FrameInfo`. /// /// Calling this function before any `Read` operations /// does not consume the frame body. pub fn read_frame_info(&mut self) -> std::io::Result { self.inner.read_frame_info() } /// Returns ownership of the reader. pub fn into_inner(self) -> R { self.inner.into_inner().into_inner() } /// Returns a mutable reference to the reader. pub fn get_mut(&mut self) -> &mut R { self.inner.get_mut().get_mut() } /// Returns a shared reference to the reader. pub fn get_ref(&self) -> &R { self.inner.get_ref().get_ref() } } impl Read for ReadDecompressor<'_, R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.inner.read(buf) } } lzzzz-2.0.0/src/lz4f/stream/decomp/write.rs000064400000000000000000000054421046102023000167430ustar 00000000000000use crate::lz4f::{Decompressor, FrameInfo, Result}; use std::{borrow::Cow, fmt, io::Write}; /// The [`Write`]-based streaming decompressor. /// /// # Example /// /// ``` /// # use std::env; /// # use std::path::Path; /// # use lzzzz::{Error, Result}; /// # use assert_fs::prelude::*; /// # let tmp_dir = assert_fs::TempDir::new().unwrap().into_persistent(); /// # env::set_current_dir(tmp_dir.path()).unwrap(); /// use lzzzz::lz4f::{compress_to_vec, WriteDecompressor}; /// use std::{fs::File, io::prelude::*}; /// /// let mut f = File::create("foo.txt")?; /// let mut w = WriteDecompressor::new(&mut f)?; /// /// let mut buf = Vec::new(); /// compress_to_vec(b"Hello world!", &mut buf, &Default::default())?; /// /// w.write_all(&buf)?; /// # Ok::<(), std::io::Error>(()) /// ``` /// /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html pub struct WriteDecompressor<'a, W: Write> { inner: W, decomp: Decompressor<'a>, } impl<'a, W: Write> WriteDecompressor<'a, W> { /// Creates a new `WriteDecompressor`. pub fn new(writer: W) -> Result { Ok(Self { inner: writer, decomp: Decompressor::new()?, }) } /// Sets the dictionary. pub fn set_dict(&mut self, dict: D) where D: Into>, { self.decomp.set_dict(dict); } /// Returns `FrameInfo` if the frame header is already decoded. /// Otherwise, returns `None`. pub fn frame_info(&self) -> Option { self.decomp.frame_info() } /// Sets the 'header-only' mode. /// /// When the 'header-only' mode is enabled, the decompressor doesn't /// consume the frame body and `write()` always returns `Ok(0)` /// if the frame header is already decoded. pub fn decode_header_only(&mut self, flag: bool) { self.decomp.decode_header_only(flag); } /// Returns a mutable reference to the writer. pub fn get_mut(&mut self) -> &mut W { &mut self.inner } /// Returns a shared reference to the writer. pub fn get_ref(&self) -> &W { &self.inner } /// Returns ownership of the writer. pub fn into_inner(self) -> W { self.inner } } impl fmt::Debug for WriteDecompressor<'_, W> where W: Write + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("WriteDecompressor") .field("writer", &self.inner) .finish() } } impl Write for WriteDecompressor<'_, W> { fn write(&mut self, buf: &[u8]) -> std::io::Result { let report = self.decomp.decompress(buf)?; self.inner.write_all(self.decomp.buf())?; self.decomp.clear_buf(); Ok(report) } fn flush(&mut self) -> std::io::Result<()> { self.inner.flush() } } lzzzz-2.0.0/src/lz4f/stream/mod.rs000064400000000000000000000000361046102023000151130ustar 00000000000000pub mod comp; pub mod decomp;