blob: 31c52f4074b87c911d1e2d2769cc5e6b73d4c863 [file] [log] [blame]
//! 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))
}