Skip to main content

lz_fear/raw/
decompress.rs

1use byteorder::{ReadBytesExt, LE};
2use std::io::{self, Cursor, Read, ErrorKind};
3use thiserror::Error;
4use culpa::{throws, throw};
5
6/// Errors when decoding a raw LZ4 block.
7#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Error)]
8pub enum DecodeError {
9    #[error("Block stream ended prematurely. Either your input was truncated or you're trying to decompress garbage.")]
10    UnexpectedEnd,
11    #[error("Refusing to decode a repetition that would exceed the memory limit. If you're using framed mode, this is either garbage input or an OOM attack. If you're using raw mode, good luck figuring out whether this input is valid or not.")]
12    MemoryLimitExceeded,
13    #[error("The offset for a deduplication is zero. This is always invalid. You are probably decoding corrupted input.")]
14    ZeroDeduplicationOffset,
15    #[error("The offset for a deduplication is out of bounds. This may be caused by a missing or incomplete dictionary.")]
16    InvalidDeduplicationOffset,
17}
18type Error = DecodeError; // do it this way for better docs
19
20impl From<io::Error> for Error {
21    fn from(e: io::Error) -> Error {
22        // this is the only kind of IO error that can happen in this code as we are always reading from slices
23        assert_eq!(e.kind(), ErrorKind::UnexpectedEof);
24        Error::UnexpectedEnd
25    }
26}
27
28/// This is how LZ4 encodes varints.
29/// Just keep reading and adding while it's all F
30#[throws]
31fn read_lsic(initial: u8, cursor: &mut Cursor<&[u8]>) -> usize {
32    let mut value: usize = initial.into();
33    if value == 0xF {
34        loop {
35            let more = cursor.read_u8()?;
36            value += usize::from(more);
37            if more != 0xff {
38                break;
39            }
40        }
41    }
42    value
43}
44
45/// Decompress an LZ4-compressed block.
46///
47/// Note that LZ4 heavily relies on a lookback mechanism where bytes earlier in the output stream are referenced.
48/// You may either pre-initialize the output buffer with this data or pass it separately in `prefix`.
49/// In particular, an LZ4 "dictionary" should (probably) be implemented as a `prefix` because you obviously
50/// don't want the dictionary to appear at the beginning of the output.
51///
52/// This function is based around memory buffers because that's what LZ4 intends.
53/// If your blocks don't fit in your memory, you should use smaller blocks.
54///
55/// `output_limit` specifies a soft upper limit for the size of `output` (including
56/// the data you passed on input). Note that this is only a measure to protect from
57/// DoS attacks and in the worst case, we may exceed it by up to `input.len()` bytes.
58#[throws]
59pub fn decompress_raw(input: &[u8], prefix: &[u8], output: &mut Vec<u8>, output_limit: usize) {
60    let mut reader = Cursor::new(input);
61    while let Ok(token) = reader.read_u8() {
62        // read literals
63        let literal_length = read_lsic(token >> 4, &mut reader)?;
64
65        let output_pos_pre_literal = output.len();
66        output.resize(output_pos_pre_literal + literal_length, 0);
67        reader.read_exact(&mut output[output_pos_pre_literal..])?;
68
69        // read duplicates
70        if let Ok(offset) = reader.read_u16::<LE>() {
71            let match_len = 4 + read_lsic(token & 0xf, &mut reader)?;
72            if (output.len() + match_len) > output_limit {
73                throw!(Error::MemoryLimitExceeded);
74            }
75            copy_overlapping(offset.into(), match_len, prefix, output)?;
76        }
77    }
78}
79
80fn copy_overlapping(offset: usize, match_len: usize, prefix: &[u8], output: &mut Vec<u8>) -> Result<(), Error> {
81    let old_len = output.len();
82    match offset {
83        0 => return Err(Error::ZeroDeduplicationOffset),
84        i if i > old_len => {
85            // need prefix for this
86            let prefix_needed = i - old_len;
87            if prefix_needed > prefix.len() {
88                return Err(Error::InvalidDeduplicationOffset);
89            }
90            let how_many_bytes_from_prefix = std::cmp::min(prefix_needed, match_len);
91            output.extend_from_slice(
92                &prefix[prefix.len() - prefix_needed..][..how_many_bytes_from_prefix],
93            );
94            let remaining_len = match_len - how_many_bytes_from_prefix;
95            if remaining_len != 0 {
96                // offset stays the same because our curser moved forward by the amount of bytes we took from prefix
97                return copy_overlapping(offset, remaining_len, &[], output);
98            }
99        }
100
101        // fastpath: memset if we repeat the same byte forever
102        1 => output.resize(old_len + match_len, output[old_len - 1]),
103
104        o if match_len <= o => {
105            // fastpath: nonoverlapping
106            // for borrowck reasons we have to extend with zeroes first and then memcpy
107            // instead of simply using extend_from_slice
108            output.resize(old_len + match_len, 0);
109            let (head, tail) = output.split_at_mut(old_len);
110            tail.copy_from_slice(&head[old_len - offset..][..match_len]);
111        }
112        2 | 4 | 8 => {
113            // fastpath: overlapping but small
114
115            // speedup: build 16 byte buffer so we can handle 16 bytes each iteration instead of one
116            let mut buf = [0u8; 16];
117            for chunk in buf.chunks_mut(offset) {
118                // if this panics (i.e. chunklen != delta), delta does not divide 16 (but it always does)
119                chunk.copy_from_slice(&output[old_len - offset..][..offset]);
120            }
121            // fill with zero bytes
122            output.resize(old_len + match_len, 0);
123            // copy buf as often as possible
124            for target in output[old_len..].chunks_mut(buf.len()) {
125                target.copy_from_slice(&buf[..target.len()]);
126            }
127        }
128        _ => {
129            // slowest path: copy single bytes
130            output.reserve(match_len);
131            for i in 0..match_len {
132                let b = output[old_len - offset + i];
133                output.push(b);
134            }
135        }
136    }
137    Ok(())
138}
139
140
141#[cfg(test)]
142pub mod test {
143    use culpa::throws;
144    use super::{decompress_raw, Error};
145
146    #[throws]
147    pub fn decompress(input: &[u8]) -> Vec<u8> {
148        let mut vec = Vec::new();
149        decompress_raw(input, &[], &mut vec, std::usize::MAX)?;
150        vec
151    }
152
153    #[test]
154    fn aaaaaaaaaaa_lots_of_aaaaaaaaa() {
155        assert_eq!(decompress(&[0x11, b'a', 1, 0]).unwrap(), b"aaaaaa");
156    }
157
158    #[test]
159    fn multiple_repeated_blocks() {
160        assert_eq!(
161            decompress(&[0x11, b'a', 1, 0, 0x22, b'b', b'c', 2, 0]).unwrap(),
162            b"aaaaaabcbcbcbc"
163        );
164    }
165
166    #[test]
167    fn all_literal() {
168        assert_eq!(decompress(&[0x30, b'a', b'4', b'9']).unwrap(), b"a49");
169    }
170
171    #[test]
172    fn offset_oob() {
173        decompress(&[0x10, b'a', 2, 0]).unwrap_err();
174        decompress(&[0x40, b'a', 1, 0]).unwrap_err();
175    }
176}