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