Skip to main content

lz4_flex/block/
decompress.rs

1//! The block decompression algorithm.
2use crate::block::{DecompressError, MINMATCH};
3use crate::fastcpy_unsafe;
4#[cfg(feature = "alloc")]
5use crate::sink::PtrSink;
6use crate::sink::Sink;
7use crate::sink::SliceSink;
8#[cfg(feature = "alloc")]
9#[allow(unused_imports)]
10use alloc::vec::Vec;
11
12/// Copies data to output_ptr by self-referential copy from start and match_length
13#[inline]
14unsafe fn duplicate(
15    output_ptr: &mut *mut u8,
16    output_end: *mut u8,
17    start: *const u8,
18    match_length: usize,
19) {
20    // We cannot simply use memcpy or `extend_from_slice`, because these do not allow
21    // self-referential copies: http://ticki.github.io/img/lz4_runs_encoding_diagram.svg
22
23    // Considering that `wild_copy_match_16` can copy up to `16 - 1` extra bytes.
24    // Defer to `duplicate_overlapping` in case of an overlapping match
25    // OR the if the wild copy would copy beyond the end of the output.
26    if (output_ptr.offset_from(start) as usize) < match_length + 16 - 1
27        || (output_end.offset_from(*output_ptr) as usize) < match_length + 16 - 1
28    {
29        duplicate_overlapping(output_ptr, start, match_length);
30    } else {
31        debug_assert!(
32            output_ptr.add(match_length / 16 * 16 + ((match_length % 16) != 0) as usize * 16)
33                <= output_end
34        );
35        wild_copy_from_src_16(start, *output_ptr, match_length);
36        *output_ptr = output_ptr.add(match_length);
37    }
38}
39
40#[inline]
41fn wild_copy_from_src_16(mut source: *const u8, mut dst_ptr: *mut u8, num_items: usize) {
42    // Note: if the compiler auto-vectorizes this it'll hurt performance!
43    // It's not the case for 16 bytes stepsize, but for 8 bytes.
44    unsafe {
45        let dst_ptr_end = dst_ptr.add(num_items);
46        loop {
47            core::ptr::copy_nonoverlapping(source, dst_ptr, 16);
48            source = source.add(16);
49            dst_ptr = dst_ptr.add(16);
50            if dst_ptr >= dst_ptr_end {
51                break;
52            }
53        }
54    }
55}
56
57/// Copy function, if the data start + match_length overlaps into output_ptr
58#[inline]
59#[cfg_attr(feature = "nightly", optimize(size))] // to avoid loop unrolling
60unsafe fn duplicate_overlapping(
61    output_ptr: &mut *mut u8,
62    mut start: *const u8,
63    match_length: usize,
64) {
65    let dst_ptr_end = output_ptr.add(match_length);
66
67    while output_ptr.add(1) < dst_ptr_end {
68        // Note that this loop unrolling is done, so that the compiler doesn't do it in a awful
69        // way.
70        // Without that the compiler will unroll/auto-vectorize the copy with a lot of branches.
71        // This is not what we want, as large overlapping copies are not that common.
72        core::ptr::copy(start, *output_ptr, 1);
73        start = start.add(1);
74        *output_ptr = output_ptr.add(1);
75
76        core::ptr::copy(start, *output_ptr, 1);
77        start = start.add(1);
78        *output_ptr = output_ptr.add(1);
79    }
80
81    if *output_ptr < dst_ptr_end {
82        core::ptr::copy(start, *output_ptr, 1);
83        *output_ptr = output_ptr.add(1);
84    }
85}
86
87#[inline]
88unsafe fn copy_from_dict(
89    output_base: *mut u8,
90    output_ptr: &mut *mut u8,
91    ext_dict: &[u8],
92    offset: usize,
93    match_length: usize,
94) -> usize {
95    // If we're here we know offset > output pos, so we have at least 1 byte to copy from dict
96    debug_assert!(output_ptr.offset_from(output_base) >= 0);
97    debug_assert!(offset > output_ptr.offset_from(output_base) as usize);
98    // offset falls within ext_dict
99    debug_assert!(ext_dict.len() + output_ptr.offset_from(output_base) as usize >= offset);
100
101    let dict_offset = ext_dict.len() + output_ptr.offset_from(output_base) as usize - offset;
102    // Can't copy past ext_dict len, the match may cross dict and output
103    let dict_match_length = match_length.min(ext_dict.len() - dict_offset);
104    // TODO test fastcpy_unsafe
105    core::ptr::copy_nonoverlapping(
106        ext_dict.as_ptr().add(dict_offset),
107        *output_ptr,
108        dict_match_length,
109    );
110    *output_ptr = output_ptr.add(dict_match_length);
111    dict_match_length
112}
113
114/// Read an integer.
115///
116/// In LZ4, we encode small integers in a way that we can have an arbitrary number of bytes. In
117/// particular, we add the bytes repeatedly until we hit a non-0xFF byte. When we do, we add
118/// this byte to our sum and terminate the loop.
119///
120/// # Example
121///
122/// ```notest
123///     255, 255, 255, 4, 2, 3, 4, 6, 7
124/// ```
125///
126/// is encoded to _255 + 255 + 255 + 4 = 769_. The bytes after the first 4 is ignored, because
127/// 4 is the first non-0xFF byte.
128#[inline]
129pub(super) fn read_integer_ptr(
130    input_ptr: &mut *const u8,
131    _input_ptr_end: *const u8,
132) -> Result<usize, DecompressError> {
133    // We start at zero and count upwards.
134    let mut n: usize = 0;
135    // If this byte takes value 255 (the maximum value it can take), another byte is read
136    // and added to the sum. This repeats until a byte lower than 255 is read.
137    loop {
138        // We add the next byte until we get a byte which we add to the counting variable.
139
140        // could be skipped with unchecked-decode
141        {
142            if *input_ptr >= _input_ptr_end {
143                return Err(DecompressError::ExpectedAnotherByte);
144            }
145        }
146        let extra = unsafe { input_ptr.read() };
147        *input_ptr = unsafe { input_ptr.add(1) };
148        n += extra as usize;
149
150        // We continue if we got 255, break otherwise.
151        if extra != 0xFF {
152            break;
153        }
154    }
155
156    // 255, 255, 255, 8
157    // 111, 111, 111, 101
158
159    Ok(n)
160}
161
162/// Read the match offset as a little-endian 16-bit integer from the input stream.
163#[inline]
164fn read_match_offset(input_ptr: &mut *const u8) -> Result<u16, DecompressError> {
165    let mut num: u16 = 0;
166    unsafe {
167        core::ptr::copy_nonoverlapping(*input_ptr, &mut num as *mut u16 as *mut u8, 2);
168        *input_ptr = input_ptr.add(2);
169    }
170
171    let offset = u16::from_le(num);
172    if offset == 0 {
173        Err(DecompressError::OffsetZero)
174    } else {
175        Ok(offset)
176    }
177}
178
179const FIT_TOKEN_MASK_LITERAL: u8 = 0b00001111;
180const FIT_TOKEN_MASK_MATCH: u8 = 0b11110000;
181
182#[test]
183fn check_token() {
184    assert!(!does_token_fit(15));
185    assert!(does_token_fit(14));
186    assert!(does_token_fit(114));
187    assert!(!does_token_fit(0b11110000));
188    assert!(does_token_fit(0b10110000));
189}
190
191/// The token consists of two parts, the literal length (upper 4 bits) and match_length (lower 4
192/// bits) if the literal length and match_length are both below 15, we don't need to read additional
193/// data, so the token does fit the metadata in a single u8.
194#[inline]
195fn does_token_fit(token: u8) -> bool {
196    !((token & FIT_TOKEN_MASK_LITERAL) == FIT_TOKEN_MASK_LITERAL
197        || (token & FIT_TOKEN_MASK_MATCH) == FIT_TOKEN_MASK_MATCH)
198}
199
200/// Decompress all bytes of `input` into `output`.
201///
202/// Returns the number of bytes written (decompressed) into `output`.
203#[inline]
204pub(crate) fn decompress_internal<const USE_DICT: bool, S: Sink>(
205    input: &[u8],
206    output: &mut S,
207    ext_dict: &[u8],
208) -> Result<usize, DecompressError> {
209    // Prevent segfault for empty input
210    if input.is_empty() {
211        return Err(DecompressError::ExpectedAnotherByte);
212    }
213
214    let ext_dict = if USE_DICT {
215        ext_dict
216    } else {
217        // ensure optimizer knows ext_dict length is 0 if !USE_DICT
218        debug_assert!(ext_dict.is_empty());
219        &[]
220    };
221    let output_base = unsafe { output.base_mut_ptr() };
222    let output_end = unsafe { output_base.add(output.capacity()) };
223    let output_start_pos_ptr = unsafe { output.base_mut_ptr().add(output.pos()) as *mut u8 };
224    let mut output_ptr = output_start_pos_ptr;
225
226    let mut input_ptr = input.as_ptr();
227    let input_ptr_end = unsafe { input.as_ptr().add(input.len()) };
228    let safe_distance_from_end =  (16 /* literal copy */ +  2 /* u16 match offset */ + 1 /* The next token to read (we can skip the check) */).min(input.len()) ;
229    let input_ptr_safe = unsafe { input_ptr_end.sub(safe_distance_from_end) };
230
231    let safe_output_ptr = unsafe {
232        let mut output_num_safe_bytes = output
233            .capacity()
234            .saturating_sub(16 /* literal copy */ + 18 /* match copy */);
235        if USE_DICT {
236            // In the dictionary case the output pointer is moved by the match length in the dictionary.
237            // This may be up to 17 bytes without exiting the loop. So we need to ensure that we have
238            // at least additional 17 bytes of space left in the output buffer in the fast loop.
239            output_num_safe_bytes = output_num_safe_bytes.saturating_sub(17);
240        };
241
242        output_base.add(output_num_safe_bytes)
243    };
244
245    // Exhaust the decoder by reading and decompressing all blocks until the remaining buffer is
246    // empty.
247    loop {
248        // Read the token. The token is the first byte in a block. It is divided into two 4-bit
249        // subtokens, the higher and the lower.
250        // This token contains to 4-bit "fields", a higher and a lower, representing the literals'
251        // length and the back reference's length, respectively.
252        let token = unsafe { input_ptr.read() };
253        input_ptr = unsafe { input_ptr.add(1) };
254
255        // Checking for hot-loop.
256        // In most cases the metadata does fit in a single 1byte token (statistically) and we are in
257        // a safe-distance to the end. This enables some optimized handling.
258        //
259        // Ideally we want to check for safe output pos like: output.pos() <= safe_output_pos; But
260        // that doesn't work when the safe_output_ptr is == output_ptr due to insufficient
261        // capacity. So we use `<` instead of `<=`, which covers that case.
262        if does_token_fit(token)
263            && (input_ptr as usize) <= input_ptr_safe as usize
264            && output_ptr < safe_output_ptr
265        {
266            let literal_length = (token >> 4) as usize;
267            let mut match_length = MINMATCH + (token & 0xF) as usize;
268
269            // output_ptr <= safe_output_ptr should guarantee we have enough space in output
270            debug_assert!(
271                unsafe { output_ptr.add(literal_length + match_length) } <= output_end,
272                "{literal_length} + {match_length} {} wont fit ",
273                literal_length + match_length
274            );
275
276            // Copy the literal
277            // The literal is at max 16 bytes, and the is_safe_distance check assures
278            // that we are far away enough from the end so we can safely copy 16 bytes
279            unsafe {
280                core::ptr::copy_nonoverlapping(input_ptr, output_ptr, 16);
281                input_ptr = input_ptr.add(literal_length);
282                output_ptr = output_ptr.add(literal_length);
283            }
284
285            // input_ptr <= input_ptr_safe should guarantee we have enough space in input
286            debug_assert!(input_ptr_end as usize - input_ptr as usize >= 2);
287            let offset = read_match_offset(&mut input_ptr)? as usize;
288
289            let output_len = unsafe { output_ptr.offset_from(output_base) as usize };
290            if offset > output_len + ext_dict.len() {
291                return Err(DecompressError::OffsetOutOfBounds);
292            }
293
294            // Check if part of the match is in the external dict
295            if USE_DICT && offset > output_len {
296                let copied = unsafe {
297                    copy_from_dict(output_base, &mut output_ptr, ext_dict, offset, match_length)
298                };
299                if copied == match_length {
300                    continue;
301                }
302                // match crosses ext_dict and output
303                match_length -= copied;
304            }
305
306            // Calculate the start of this duplicate segment. At this point offset was already
307            // checked to be in bounds and the external dictionary copy, if any, was
308            // already copied and subtracted from match_length.
309            let start_ptr = unsafe { output_ptr.sub(offset) };
310            debug_assert!(start_ptr >= output_base);
311            debug_assert!(start_ptr < output_end);
312            debug_assert!(unsafe { output_end.offset_from(start_ptr) as usize } >= match_length);
313
314            // In this branch we know that match_length is at most 18 (14 + MINMATCH).
315            // But the blocks can overlap, so make sure they are at least 18 bytes apart
316            // to enable an optimized copy of 18 bytes.
317            if offset >= match_length {
318                unsafe {
319                    // _copy_, not copy_non_overlapping, as it may overlap.
320                    // Compiles to the same assembly on x68_64.
321                    core::ptr::copy(start_ptr, output_ptr, 18);
322                    output_ptr = output_ptr.add(match_length);
323                }
324            } else {
325                unsafe {
326                    duplicate_overlapping(&mut output_ptr, start_ptr, match_length);
327                }
328            }
329
330            continue;
331        }
332
333        // Now, we read the literals section.
334        // Literal Section
335        // If the initial value is 15, it is indicated that another byte will be read and added to
336        // it
337        let mut literal_length = (token >> 4) as usize;
338        if literal_length != 0 {
339            if literal_length == 15 {
340                // The literal_length length took the maximal value, indicating that there is more
341                // than 15 literal_length bytes. We read the extra integer.
342                literal_length += read_integer_ptr(&mut input_ptr, input_ptr_end)? as usize;
343            }
344
345            // could be skipped with unchecked-decode
346            {
347                // Check if literal is out of bounds for the input, and if there is enough space on
348                // the output
349                if literal_length > input_ptr_end as usize - input_ptr as usize {
350                    return Err(DecompressError::LiteralOutOfBounds);
351                }
352                if literal_length > unsafe { output_end.offset_from(output_ptr) as usize } {
353                    return Err(DecompressError::OutputTooSmall {
354                        expected: unsafe { output_ptr.offset_from(output_base) as usize }
355                            + literal_length,
356                        actual: output.capacity(),
357                    });
358                }
359            }
360            unsafe {
361                fastcpy_unsafe::slice_copy(input_ptr, output_ptr, literal_length);
362                output_ptr = output_ptr.add(literal_length);
363                input_ptr = input_ptr.add(literal_length);
364            }
365        }
366
367        // If the input stream is emptied, we break out of the loop. This is only the case
368        // in the end of the stream, since the block is intact otherwise.
369        if input_ptr >= input_ptr_end {
370            break;
371        }
372
373        // Read duplicate section
374        // could be skipped with unchecked-decode
375        {
376            if (input_ptr_end as usize) - (input_ptr as usize) < 2 {
377                return Err(DecompressError::ExpectedAnotherByte);
378            }
379        }
380        let offset = read_match_offset(&mut input_ptr)? as usize;
381        // Obtain the initial match length. The match length is the length of the duplicate segment
382        // which will later be copied from data previously decompressed into the output buffer. The
383        // initial length is derived from the second part of the token (the lower nibble), we read
384        // earlier. Since having a match length of less than 4 would mean negative compression
385        // ratio, we start at 4 (MINMATCH).
386
387        // The initial match length can maximally be 19 (MINMATCH + 15). As with the literal length,
388        // this indicates that there are more bytes to read.
389        let mut match_length = MINMATCH + (token & 0xF) as usize;
390        if match_length == MINMATCH + 15 {
391            // The match length took the maximal value, indicating that there is more bytes. We
392            // read the extra integer.
393            match_length += read_integer_ptr(&mut input_ptr, input_ptr_end)? as usize;
394        }
395
396        // We now copy from the already decompressed buffer. This allows us for storing duplicates
397        // by simply referencing the other location.
398        let output_len = unsafe { output_ptr.offset_from(output_base) as usize };
399
400        // could be skipped with unchecked-decode
401        {
402            if offset > output_len + ext_dict.len() {
403                return Err(DecompressError::OffsetOutOfBounds);
404            }
405            if match_length > unsafe { output_end.offset_from(output_ptr) as usize } {
406                return Err(DecompressError::OutputTooSmall {
407                    expected: output_len + match_length,
408                    actual: output.capacity(),
409                });
410            }
411        }
412
413        if USE_DICT && offset > output_len {
414            let copied = unsafe {
415                copy_from_dict(output_base, &mut output_ptr, ext_dict, offset, match_length)
416            };
417            if copied == match_length {
418                // could be skipped with unchecked-decode
419                {
420                    if input_ptr >= input_ptr_end {
421                        return Err(DecompressError::ExpectedAnotherByte);
422                    }
423                }
424
425                continue;
426            }
427            // match crosses ext_dict and output
428            match_length -= copied;
429        }
430
431        // Calculate the start of this duplicate segment. At this point offset was already checked
432        // to be in bounds and the external dictionary copy, if any, was already copied and
433        // subtracted from match_length.
434        let start_ptr = unsafe { output_ptr.sub(offset) };
435        debug_assert!(start_ptr >= output_base);
436        debug_assert!(start_ptr < output_end);
437        debug_assert!(unsafe { output_end.offset_from(start_ptr) as usize } >= match_length);
438        unsafe {
439            duplicate(&mut output_ptr, output_end, start_ptr, match_length);
440        }
441        // could be skipped with unchecked-decode
442        {
443            if input_ptr >= input_ptr_end {
444                return Err(DecompressError::ExpectedAnotherByte);
445            }
446        }
447    }
448    unsafe {
449        output.set_pos(output_ptr.offset_from(output_base) as usize);
450        Ok(output_ptr.offset_from(output_start_pos_ptr) as usize)
451    }
452}
453
454/// Decompress all bytes of `input` into `output`.
455/// `output` should be preallocated with a size of of the uncompressed data.
456#[inline]
457pub fn decompress_into(input: &[u8], output: &mut [u8]) -> Result<usize, DecompressError> {
458    decompress_internal::<false, _>(input, &mut SliceSink::new(output, 0), b"")
459}
460
461/// Decompress all bytes of `input` into `output`.
462///
463/// Returns the number of bytes written (decompressed) into `output`.
464#[inline]
465pub fn decompress_into_with_dict(
466    input: &[u8],
467    output: &mut [u8],
468    ext_dict: &[u8],
469) -> Result<usize, DecompressError> {
470    decompress_internal::<true, _>(input, &mut SliceSink::new(output, 0), ext_dict)
471}
472
473/// Decompress all bytes of `input` into a new vec.
474/// The passed parameter `min_uncompressed_size` needs to be equal or larger than the uncompressed size.
475///
476/// # Panics
477/// May panic if the parameter `min_uncompressed_size` is smaller than the
478/// uncompressed data.
479
480#[cfg(feature = "alloc")]
481#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
482#[inline]
483pub fn decompress_with_dict(
484    input: &[u8],
485    min_uncompressed_size: usize,
486    ext_dict: &[u8],
487) -> Result<Vec<u8>, DecompressError> {
488    // Allocate a vector to contain the decompressed stream.
489    let mut vec = Vec::with_capacity(min_uncompressed_size);
490    let decomp_len =
491        decompress_internal::<true, _>(input, &mut PtrSink::from_vec(&mut vec, 0), ext_dict)?;
492    unsafe {
493        vec.set_len(decomp_len);
494    }
495    Ok(vec)
496}
497
498/// Decompress all bytes of `input` into a new vec. The first 4 bytes are the uncompressed size in
499/// little endian. Can be used in conjunction with `compress_prepend_size`
500#[cfg(feature = "alloc")]
501#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
502#[inline]
503pub fn decompress_size_prepended(input: &[u8]) -> Result<Vec<u8>, DecompressError> {
504    let (uncompressed_size, input) = super::uncompressed_size(input)?;
505    decompress(input, uncompressed_size)
506}
507
508/// Decompress all bytes of `input` into a new vec.
509/// The passed parameter `min_uncompressed_size` needs to be equal or larger than the uncompressed size.
510///
511/// # Panics
512/// May panic if the parameter `min_uncompressed_size` is smaller than the
513/// uncompressed data.
514#[cfg(feature = "alloc")]
515#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
516#[inline]
517pub fn decompress(input: &[u8], min_uncompressed_size: usize) -> Result<Vec<u8>, DecompressError> {
518    // Allocate a vector to contain the decompressed stream.
519    let mut vec = Vec::with_capacity(min_uncompressed_size);
520    let decomp_len =
521        decompress_internal::<true, _>(input, &mut PtrSink::from_vec(&mut vec, 0), b"")?;
522    unsafe {
523        vec.set_len(decomp_len);
524    }
525    Ok(vec)
526}
527
528/// Decompress all bytes of `input` into a new vec. The first 4 bytes are the uncompressed size in
529/// little endian. Can be used in conjunction with `compress_prepend_size_with_dict`
530#[cfg(feature = "alloc")]
531#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
532#[inline]
533pub fn decompress_size_prepended_with_dict(
534    input: &[u8],
535    ext_dict: &[u8],
536) -> Result<Vec<u8>, DecompressError> {
537    let (uncompressed_size, input) = super::uncompressed_size(input)?;
538    decompress_with_dict(input, uncompressed_size, ext_dict)
539}
540
541#[cfg(test)]
542mod test {
543    use super::*;
544
545    #[test]
546    fn all_literal() {
547        assert_eq!(decompress(&[0x30, b'a', b'4', b'9'], 3).unwrap(), b"a49");
548    }
549
550    #[test]
551    fn incomplete_input() {
552        assert!(matches!(
553            decompress(&[], 255),
554            Err(DecompressError::ExpectedAnotherByte)
555        ));
556        assert!(matches!(
557            // incomplete literal len
558            decompress(&[0xF0], 255),
559            Err(DecompressError::ExpectedAnotherByte)
560        ));
561        assert!(matches!(
562            // incomplete match offset
563            decompress(&[0x0F, 0], 255),
564            Err(DecompressError::ExpectedAnotherByte)
565        ));
566        assert!(matches!(
567            // incomplete match len
568            decompress(&[0x0F, 1, 0], 255),
569            Err(DecompressError::ExpectedAnotherByte)
570        ));
571    }
572
573    // this error test is only valid in safe-decode.
574    #[test]
575    fn offset_oob() {
576        // incomplete literal
577        assert!(matches!(
578            decompress(&[0x40, b'a', 1, 0], 4),
579            Err(DecompressError::LiteralOutOfBounds)
580        ));
581        // literal too large for output
582        assert!(matches!(
583            decompress(&[0x20, b'a', b'a', 1, 0], 1),
584            Err(DecompressError::OutputTooSmall {
585                expected: 2,
586                actual: 1
587            })
588        ));
589        // match too large for output
590        assert!(matches!(
591            decompress(&[0x10, b'a', 1, 0], 4),
592            Err(DecompressError::OutputTooSmall {
593                expected: 5,
594                actual: 4
595            })
596        ));
597
598        // out-of-bounds hot-loop
599        assert!(matches!(
600            decompress(
601                &[0x0E, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
602                256
603            ),
604            Err(DecompressError::OffsetOutOfBounds)
605        ));
606        // out-of-bounds for dict
607        assert!(matches!(
608            decompress_with_dict(
609                &[0x0E, 255, 0, 0x70, 0, 0, 0, 0, 0, 0, 0],
610                256,
611                &[0_u8; 250]
612            ),
613            Err(DecompressError::OffsetOutOfBounds)
614        ));
615        // out-of-bounds non-hot-loop overlapping
616        assert!(matches!(
617            decompress(&[0x0F, 1, 0, 1, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
618            Err(DecompressError::OffsetOutOfBounds)
619        ));
620        // out-of-bounds non-hot-loop non-overlapping
621        assert!(matches!(
622            decompress(&[0x40, 0, 0, 0, 0, 255, 0, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
623            Err(DecompressError::OffsetOutOfBounds)
624        ));
625    }
626
627    #[test]
628    fn offset_0() {
629        assert!(matches!(
630            decompress(&[0x0E, 0, 0, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
631            Err(DecompressError::OffsetZero)
632        ));
633    }
634}