Skip to main content

lz_fear/framed/
compress.rs

1use byteorder::{LE, WriteBytesExt};
2use std::hash::Hasher;
3use std::io::{self, Read, Write, Seek, SeekFrom, ErrorKind};
4use std::mem;
5use twox_hash::XxHash32;
6use thiserror::Error;
7use culpa::{throws};
8
9use super::{MAGIC, INCOMPRESSIBLE, WINDOW_SIZE};
10use super::header::{Flags, BlockDescriptor};
11use crate::raw::{U32Table, compress2, EncoderTable};
12
13
14/// Errors when compressing an LZ4 frame.
15#[derive(Error, Debug)]
16pub enum CompressionError {
17    #[error("error reading from the input you gave me")]
18    ReadError(io::Error),
19    #[error("error writing to the output you gave me")]
20    WriteError(#[from] io::Error),
21    #[error("the block size you asked for is not supported")]
22    InvalidBlockSize,
23}
24type Error = CompressionError; // do it this way for better docs
25impl From<Error> for io::Error {
26    fn from(e: Error) -> io::Error {
27        io::Error::new(ErrorKind::Other, e)
28    }
29}
30
31/// A builder-style struct that configures compression settings.
32/// This is how you compress LZ4 frames.
33/// (An LZ4 file usually consists of a single frame.)
34///
35/// Create it using `Default::default()`.
36pub struct CompressionSettings<'a> {
37    independent_blocks: bool,
38    block_checksums: bool,
39    content_checksum: bool,
40    block_size: usize,
41    dictionary: Option<&'a [u8]>,
42    dictionary_id: Option<u32>,
43}
44impl<'a> Default for CompressionSettings<'a> {
45    fn default() -> Self {
46        Self {
47            independent_blocks: true,
48            block_checksums: false,
49            content_checksum: true,
50            block_size: 4 * 1024 * 1024,
51            dictionary: None,
52            dictionary_id: None,
53        }
54    }
55}
56impl<'a> CompressionSettings<'a> {
57    /// In independent mode, blocks are not allowed to reference data from previous blocks.
58    /// Hence, using dependent blocks yields slightly better compression.
59    /// The downside of dependent blocks is that seeking becomes impossible - the entire frame always has
60    /// to be decompressed from the beginning.
61    ///
62    /// Blocks are independent by default.
63    pub fn independent_blocks(&mut self, v: bool) -> &mut Self {
64        self.independent_blocks = v;
65        self
66    }
67
68    /// Block checksums can help detect data corruption in storage and transit.
69    /// They do not offer error correction though.
70    ///
71    /// In most cases, block checksums are not very helpful because you generally want a lower
72    /// layer to deal with data corruption more comprehensively.
73    ///
74    /// Block checksums are disabled by default.
75    pub fn block_checksums(&mut self, v: bool) -> &mut Self {
76        self.block_checksums = v;
77        self
78    }
79
80    /// The content checksum (also called frame checksum) is calculated over the contents of the entire frame.
81    /// This makes them cheaper than block checksums as their size overhead is constant
82    /// as well as marginally more useful, because they can help protect against incorrect decompression.
83    ///
84    /// Note that the content checksum can only be verified *after* the entire frame has been read
85    /// (and returned!), which is the downside of content checksums.
86    ///
87    /// Frame checksums are enabled by default.
88    pub fn content_checksum(&mut self, v: bool) -> &mut Self {
89        self.content_checksum = v;
90        self
91    }
92
93    /// Only valid values are 4MiB, 1MiB, 256KiB, 64KiB
94    /// (TODO: better interface for this)
95    ///
96    /// The default block size is 4 MiB.
97    pub fn block_size(&mut self, v: usize) -> &mut Self {
98        self.block_size = v;
99        self
100    }
101
102    /// A dictionary is essentially a constant slice of bytes shared by the compressing and decompressing party.
103    /// Using a dictionary can improve compression ratios, because the compressor can reference data from the dictionary.
104    ///
105    /// The dictionary id is an application-specific identifier which can be used during decompression to determine
106    /// which dictionary to use.
107    ///
108    /// Note that while the size of a dictionary can be arbitrary, dictionaries larger than 64 KiB are not useful as
109    /// the LZ4 algorithm does not support backreferences by more than 64 KiB, i.e. any dictionary content before
110    /// the trailing 64 KiB is silently ignored.
111    ///
112    /// By default, no dictionary is used and no id is specified.
113    pub fn dictionary(&mut self, id: u32, dict: &'a [u8]) -> &mut Self {
114        self.dictionary_id = Some(id);
115        self.dictionary = Some(dict);
116        self
117    }
118
119    /// The dictionary id header field is quite obviously intended to tell anyone trying to decompress your frame which dictionary to use.
120    /// So it is only natural to assume that the *absence* of a dictionary id indicates that no dictionary was used.
121    ///
122    /// Unfortunately this assumption turns out to be incorrect. The LZ4 CLI simply never writes a dictionary id.
123    /// The major downside is that you can no longer distinguish corrupted data from a missing dictionary
124    /// (unless you write block checksums, which the LZ4 CLI also never does).
125    ///
126    /// Hence, this library is opinionated in the sense that we always want you to specify either neither or both of these things
127    /// (the LZ4 CLI basically just ignores the dictionary id completely and only cares about whether you specify a dictionary parameter or not).
128    ///
129    /// If you think you know better (you probably don't) you may use this method to break this rule.
130    pub fn dictionary_id_nonsense_override(&mut self, id: Option<u32>) -> &mut Self {
131        self.dictionary_id = id;
132        self
133    }
134
135    // TODO: these interfaces need to go away in favor of something that can handle individual blocks rather than always compressing full frames at once
136
137    #[throws]
138    pub fn compress<R: Read, W: Write>(&self, reader: R, writer: W) {
139        self.compress_internal(reader, writer, None)?;
140    }
141
142    #[throws]
143    pub fn compress_with_size_unchecked<R: Read, W: Write>(&self, reader: R, writer: W, content_size: u64) {
144        self.compress_internal(reader, writer, Some(content_size))?;
145    }
146
147    #[throws]
148    pub fn compress_with_size<R: Read + Seek, W: Write>(&self, mut reader: R, writer: W) {
149        // maybe one day we can just use reader.stream_len() here: https://github.com/rust-lang/rust/issues/59359
150        // then again, we implement this to ignore the all bytes before the cursor which stream_len() does not
151        let start = reader.stream_position()?;
152        let end = reader.seek(SeekFrom::End(0))?;
153        reader.seek(SeekFrom::Start(start))?;
154
155        let length = end - start;
156        self.compress_internal(reader, writer, Some(length))?;
157    }
158
159    #[throws]
160    fn compress_internal<R: Read, W: Write>(&self, mut reader: R, mut writer: W, content_size: Option<u64>) {
161        let mut content_hasher = None;
162
163        let mut flags = Flags::empty();
164        if self.independent_blocks {
165            flags |= Flags::IndependentBlocks;
166        }
167        if self.block_checksums {
168            flags |= Flags::BlockChecksums;
169        }
170        if self.content_checksum {
171            flags |= Flags::ContentChecksum;
172            content_hasher = Some(XxHash32::with_seed(0));
173        }
174        if self.dictionary_id.is_some() {
175            flags |= Flags::DictionaryId;
176        }
177        if content_size.is_some() {
178            flags |= Flags::ContentSize;
179        }
180
181        let version = 1 << 6;
182        let flag_byte = version | flags.bits();
183        let bd_byte = BlockDescriptor::new(self.block_size).ok_or(Error::InvalidBlockSize)?.0;
184
185        let mut header = Vec::new();
186        header.write_u32::<LE>(MAGIC)?;
187        header.write_u8(flag_byte)?;
188        header.write_u8(bd_byte)?;
189        
190        if let Some(content_size) = content_size {
191            header.write_u64::<LE>(content_size)?;
192        }
193        if let Some(id) = self.dictionary_id {
194            header.write_u32::<LE>(id)?;
195        }
196
197        let mut hasher = XxHash32::with_seed(0);
198        hasher.write(&header[4..]); // skip magic for header checksum
199        header.write_u8((hasher.finish() >> 8) as u8)?;
200        writer.write_all(&header)?;
201
202        let mut template_table = U32Table::default();
203        let mut block_initializer: &[u8] = &[];
204        if let Some(dict) = self.dictionary {
205            for window in dict.windows(mem::size_of::<usize>()).step_by(3) {
206                // this is a perfectly safe way to find out where our window is pointing
207                // we could do this manually by iterating with an index to avoid the scary-looking
208                // pointer math but this is way more convenient IMO
209                let offset = window.as_ptr() as usize - dict.as_ptr() as usize;
210                template_table.replace(dict, offset);
211            }
212
213            block_initializer = dict;
214        }
215
216        // TODO: when doing dependent blocks or dictionaries, in_buffer's capacity is insufficient
217        let mut in_buffer = Vec::with_capacity(self.block_size);
218        in_buffer.extend_from_slice(block_initializer);
219        let mut out_buffer = vec![0u8; self.block_size];
220        let mut table = template_table.clone();
221        loop {
222            let window_offset = in_buffer.len();
223
224            // We basically want read_exact semantics, except at the end.
225            // Sadly read_exact specifies the buffer contents to be undefined
226            // on error, so we have to use this construction instead.
227            reader.by_ref().take(self.block_size as u64).read_to_end(&mut in_buffer).map_err(Error::ReadError)?;
228            let read_bytes = in_buffer.len() - window_offset;
229            if read_bytes == 0 {
230                break;
231            }
232            
233            if let Some(x) = content_hasher.as_mut() {
234                x.write(&in_buffer[window_offset..]);
235            }
236
237            // TODO: implement u16 table for small inputs
238
239            // 1. limit output by input size so we never have negative compression ratio
240            // 2. use a wrapper that forbids partial writes, so don't write 32-bit integers
241            //    as four individual bytes with four individual range checks
242            let mut cursor = NoPartialWrites(&mut out_buffer[..read_bytes]);
243            let write = match compress2(&in_buffer, window_offset, &mut table, &mut cursor) {
244                Ok(()) => {
245                    let not_written_len = cursor.0.len();
246                    let written_len = read_bytes - not_written_len;
247                    writer.write_u32::<LE>(written_len as u32)?;
248                    &out_buffer[..written_len]
249                }
250                Err(e) => {
251                    assert!(e.kind() == ErrorKind::ConnectionAborted);
252                    // incompressible
253                    writer.write_u32::<LE>((read_bytes as u32) | INCOMPRESSIBLE)?;
254                    &in_buffer[window_offset..]
255                }
256            };
257
258            writer.write_all(write)?;
259            if flags.contains(Flags::BlockChecksums) {
260                let mut block_hasher = XxHash32::with_seed(0);
261                block_hasher.write(write);
262                writer.write_u32::<LE>(block_hasher.finish() as u32)?;
263            }
264
265            if flags.contains(Flags::IndependentBlocks) {
266                // clear table
267                in_buffer.clear();
268                in_buffer.extend_from_slice(block_initializer);
269
270                table = template_table.clone();
271            } else if in_buffer.len() > WINDOW_SIZE {
272                let how_much_to_forget = in_buffer.len() - WINDOW_SIZE;
273                table.offset(how_much_to_forget);
274                in_buffer.drain(..how_much_to_forget);
275            }
276        }
277        writer.write_u32::<LE>(0)?;
278
279        if let Some(x) = content_hasher {
280            writer.write_u32::<LE>(x.finish() as u32)?;
281        }
282    }
283}
284
285/// Helper struct to allow more efficient code generation when using the Write trait on byte buffers.
286///
287/// The underlying problem is that the Write impl on [u8] (and everything similar, e.g. Cursor<[u8]>)
288/// is specified to write as many bytes as possible before returning an error.
289/// This is a problem because it forces e.g. a 32-bit write to compile to four 8-bit writes with a range
290/// check every time, rather than a single 32-bit write with a range check.
291///
292/// This wrapper aims to resolve the problem by simply not writing anything in case we fail the bounds check,
293/// as we throw away the entire buffer in that case anyway.
294struct NoPartialWrites<'a>(&'a mut [u8]);
295impl<'a> Write for NoPartialWrites<'a> {
296    #[inline]
297    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
298        if self.0.len() < data.len() {
299            // quite frankly it doesn't matter what we specify here
300            return Err(ErrorKind::ConnectionAborted.into());
301        }
302
303        let amt = data.len();
304        let (a, b) = mem::take(&mut self.0).split_at_mut(data.len());
305        a.copy_from_slice(data);
306        self.0 = b;
307        Ok(amt)
308    }
309
310    #[inline]
311    fn flush(&mut self) -> io::Result<()> {
312        Ok(())
313    }
314}
315