1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
//! 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::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))
}