Skip to main content

craft_codec/
compression.rs

1use crate::{Compression, Error, Result};
2
3// Keep codec state inline so conversion requires no extra allocation.
4#[allow(clippy::large_enum_variant)]
5pub(crate) enum CompressionState {
6    None,
7    #[cfg(feature = "lz4")]
8    Lz4(Lz4),
9}
10
11impl CompressionState {
12    pub(crate) fn new(parameters: Compression) -> Result<Self> {
13        match parameters {
14            Compression::None => Ok(Self::None),
15            #[cfg(feature = "lz4")]
16            Compression::Lz4 => Ok(Self::Lz4(Lz4::new())),
17            #[cfg(not(feature = "lz4"))]
18            Compression::Lz4 => Err(Error::CompressionUnavailable),
19        }
20    }
21
22    pub(crate) fn encode(&mut self, _buffer: &mut Vec<u8>) -> Result<()> {
23        match self {
24            Self::None => Ok(()),
25            #[cfg(feature = "lz4")]
26            Self::Lz4(codec) => codec.encode(_buffer),
27        }
28    }
29
30    pub(crate) fn decode(&mut self, _buffer: &mut Vec<u8>, _raw_len: usize) -> Result<()> {
31        match self {
32            Self::None => Err(Error::DecompressionFailed),
33            #[cfg(feature = "lz4")]
34            Self::Lz4(codec) => codec.decode(_buffer, _raw_len),
35        }
36    }
37}
38
39/// Independent LZ4 blocks with reusable scratch and a lazily initialized hash table.
40/// Decoder-only instances never allocate a compression table. No size prefix,
41/// dictionary, checksum, or LZ4 frame wrapper is stored in the data stream.
42#[cfg(feature = "lz4")]
43#[derive(Default)]
44pub(crate) struct Lz4 {
45    scratch: Vec<u8>,
46    table: Option<lz4_flex::block::CompressTable>,
47}
48
49#[cfg(feature = "lz4")]
50impl Lz4 {
51    /// Create a codec without allocating scratch buffers.
52    pub(crate) fn new() -> Self {
53        Self::default()
54    }
55}
56
57#[cfg(feature = "lz4")]
58impl Lz4 {
59    fn encode(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
60        use lz4_flex::block::{CompressTable, compress_into_with_table, get_maximum_output_size};
61        // The profile limits raw lengths so the codec's bound cannot overflow.
62        crate::error::resize(&mut self.scratch, get_maximum_output_size(buffer.len()))?;
63        let table = self.table.get_or_insert_with(CompressTable::large);
64        let written = compress_into_with_table(buffer, &mut self.scratch, table)
65            .map_err(|_| crate::Error::CompressionFailed)?;
66        if written < buffer.len() {
67            self.scratch.truncate(written);
68            std::mem::swap(buffer, &mut self.scratch);
69        }
70        Ok(())
71    }
72
73    fn decode(&mut self, buffer: &mut Vec<u8>, raw_len: usize) -> Result<()> {
74        crate::error::resize(&mut self.scratch, raw_len)?;
75        // The block API consumes the complete input, not a prefix of a stream.
76        let written = lz4_flex::block::decompress_into(buffer, &mut self.scratch)
77            .map_err(|_| crate::Error::DecompressionFailed)?;
78        if written != raw_len {
79            return Err(crate::Error::DecompressionFailed);
80        }
81        std::mem::swap(buffer, &mut self.scratch);
82        Ok(())
83    }
84}