| //! LZ4 Block Format |
| //! |
| //! As defined in <https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md> |
| //! |
| //! 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)) |
| } |