//! LZ4 Block Format //! //! As defined in //! //! Currently for no_std support only the block format is supported. //! //! # Example: block format roundtrip //! ``` //! use lz4_flex::block::{compress_prepend_size, decompress_size_prepended}; //! let input: &[u8] = b"Hello people, what's up?"; //! let compressed = compress_prepend_size(input); //! let uncompressed = decompress_size_prepended(&compressed).unwrap(); //! assert_eq!(input, uncompressed); //! ``` //! #[cfg_attr(feature = "safe-encode", forbid(unsafe_code))] pub(crate) mod compress; pub(crate) mod hashtable; #[cfg(feature = "safe-decode")] #[cfg_attr(feature = "safe-decode", forbid(unsafe_code))] pub(crate) mod decompress_safe; #[cfg(feature = "safe-decode")] pub(crate) use decompress_safe as decompress; #[cfg(not(feature = "safe-decode"))] pub(crate) mod decompress; pub use compress::*; pub use decompress::*; use core::convert::TryInto; use core::fmt; pub(crate) const WINDOW_SIZE: usize = 64 * 1024; /// https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md#end-of-block-restrictions /// The last match must start at least 12 bytes before the end of block. The last match is part of /// the penultimate sequence. It is followed by the last sequence, which contains only literals. /// /// Note that, as a consequence, an independent block < 13 bytes cannot be compressed, because the /// match must copy "something", so it needs at least one prior byte. /// /// When a block can reference data from another block, it can start immediately with a match and no /// literal, so a block of 12 bytes can be compressed. const MFLIMIT: usize = 12; /// The last 5 bytes of input are always literals. Therefore, the last sequence contains at least 5 /// bytes. const LAST_LITERALS: usize = 5; /// Due the way the compression loop is arrange we may read up to (register_size - 2) bytes from the /// current position. So we must end the matches 6 bytes before the end, 1 more than required by the /// spec. const END_OFFSET: usize = LAST_LITERALS + 1; /// https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md#end-of-block-restrictions /// Minimum length of a block /// /// MFLIMIT + 1 for the token. const LZ4_MIN_LENGTH: usize = MFLIMIT + 1; const MAXD_LOG: usize = 16; const MAX_DISTANCE: usize = (1 << MAXD_LOG) - 1; #[allow(dead_code)] const MATCH_LENGTH_MASK: u32 = (1_u32 << 4) - 1; // 0b1111 / 15 /// The minimum length of a duplicate const MINMATCH: usize = 4; #[allow(dead_code)] const FASTLOOP_SAFE_DISTANCE: usize = 64; /// Switch for the hashtable size byU16 #[allow(dead_code)] static LZ4_64KLIMIT: usize = (64 * 1024) + (MFLIMIT - 1); /// An error representing invalid compressed data. #[derive(Debug)] #[non_exhaustive] pub enum DecompressError { /// The provided output is too small OutputTooSmall { /// Minimum expected output size expected: usize, /// Actual size of output actual: usize, }, /// Literal is out of bounds of the input LiteralOutOfBounds, /// Expected another byte, but none found. ExpectedAnotherByte, /// Deduplication offset out of bounds (not in buffer). OffsetOutOfBounds, } #[derive(Debug)] #[non_exhaustive] /// Errors that can happen during compression. pub enum CompressError { /// The provided output is too small. OutputTooSmall, } impl fmt::Display for DecompressError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { DecompressError::OutputTooSmall { expected, actual } => { write!( f, "provided output is too small for the decompressed data, actual {actual}, expected \ {expected}" ) } DecompressError::LiteralOutOfBounds => { f.write_str("literal is out of bounds of the input") } DecompressError::ExpectedAnotherByte => { f.write_str("expected another byte, found none") } DecompressError::OffsetOutOfBounds => { f.write_str("the offset to copy is not contained in the decompressed buffer") } } } } impl fmt::Display for CompressError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { CompressError::OutputTooSmall => f.write_str( "output is too small for the compressed data, use get_maximum_output_size to \ reserve enough space", ), } } } #[cfg(feature = "std")] impl std::error::Error for DecompressError {} #[cfg(feature = "std")] impl std::error::Error for CompressError {} /// This can be used in conjunction with `decompress_size_prepended`. /// It will read the first 4 bytes as little-endian encoded length, and return /// the rest of the bytes after the length encoding. #[inline] pub fn uncompressed_size(input: &[u8]) -> Result<(usize, &[u8]), DecompressError> { let size = input.get(..4).ok_or(DecompressError::ExpectedAnotherByte)?; let size: &[u8; 4] = size.try_into().unwrap(); let uncompressed_size = u32::from_le_bytes(*size) as usize; let rest = &input[4..]; Ok((uncompressed_size, rest)) }