Skip to main content

craft_codec/
compression.rs

1use crate::{Compression, Identity, Result};
2
3/// Sealed compression implementation used for static dispatch.
4/// Applications normally pass [`Identity`] or, with feature `lz4`, `Lz4`.
5pub trait CompressionCodec: crate::sealed::Sealed {
6    /// Metadata format implemented by this codec.
7    const FORMAT: Compression;
8    /// Compress one block, retaining the original if compression is not smaller.
9    #[doc(hidden)]
10    fn encode(&mut self, buffer: &mut Vec<u8>) -> Result<()>;
11    /// Decode a compressed block to exactly `raw_len` bytes.
12    #[doc(hidden)]
13    fn decode(&mut self, buffer: &mut Vec<u8>, raw_len: usize) -> Result<()>;
14}
15
16impl CompressionCodec for Identity {
17    const FORMAT: Compression = Compression::None;
18    fn encode(&mut self, _: &mut Vec<u8>) -> Result<()> {
19        Ok(())
20    }
21    fn decode(&mut self, _: &mut Vec<u8>, _: usize) -> Result<()> {
22        Err(crate::Error::DecompressionFailed)
23    }
24}
25
26/// Independent LZ4 blocks with reusable scratch and a lazily initialized hash table.
27/// Decoder-only instances never allocate a compression table. No size prefix,
28/// dictionary, checksum, or LZ4 frame wrapper is stored in the data stream.
29#[cfg(feature = "lz4")]
30#[derive(Default)]
31pub struct Lz4 {
32    scratch: Vec<u8>,
33    table: Option<lz4_flex::block::CompressTable>,
34}
35
36#[cfg(feature = "lz4")]
37impl Lz4 {
38    /// Create a codec without allocating scratch buffers.
39    pub fn new() -> Self {
40        Self::default()
41    }
42}
43
44#[cfg(feature = "lz4")]
45impl crate::sealed::Sealed for Lz4 {}
46
47#[cfg(feature = "lz4")]
48impl CompressionCodec for Lz4 {
49    const FORMAT: Compression = Compression::Lz4;
50
51    fn encode(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
52        use lz4_flex::block::{CompressTable, compress_into_with_table, get_maximum_output_size};
53        // The profile limits raw lengths so the codec's bound cannot overflow.
54        crate::error::resize(&mut self.scratch, get_maximum_output_size(buffer.len()))?;
55        let table = self.table.get_or_insert_with(CompressTable::large);
56        let written = compress_into_with_table(buffer, &mut self.scratch, table)
57            .map_err(|_| crate::Error::CompressionFailed)?;
58        if written < buffer.len() {
59            self.scratch.truncate(written);
60            std::mem::swap(buffer, &mut self.scratch);
61        }
62        Ok(())
63    }
64
65    fn decode(&mut self, buffer: &mut Vec<u8>, raw_len: usize) -> Result<()> {
66        crate::error::resize(&mut self.scratch, raw_len)?;
67        // The block API consumes the complete input, not a prefix of a stream.
68        let written = lz4_flex::block::decompress_into(buffer, &mut self.scratch)
69            .map_err(|_| crate::Error::DecompressionFailed)?;
70        if written != raw_len {
71            return Err(crate::Error::DecompressionFailed);
72        }
73        std::mem::swap(buffer, &mut self.scratch);
74        Ok(())
75    }
76}