Skip to main content

lz4/
sys.rs

1#![allow(non_camel_case_types)]
2#![allow(non_snake_case)]
3#![allow(clippy::missing_safety_doc)]
4#![allow(clippy::too_many_arguments)]
5#![allow(clippy::needless_range_loop)]
6
7pub use libc::{c_char, c_int, c_uint, c_ulonglong, c_void, size_t};
8use std::cmp;
9use std::ptr;
10use std::slice;
11
12#[derive(Clone, Copy, Debug)]
13#[repr(C)]
14pub struct LZ4FCompressionContext(pub *mut c_void);
15unsafe impl Send for LZ4FCompressionContext {}
16
17#[derive(Clone, Copy, Debug)]
18#[repr(C)]
19pub struct LZ4FDecompressionContext(pub *mut c_void);
20unsafe impl Send for LZ4FDecompressionContext {}
21
22pub type LZ4FErrorCode = size_t;
23
24#[derive(Clone, Debug)]
25#[repr(u32)]
26pub enum BlockSize {
27    Default = 0,
28    Max64KB = 4,
29    Max256KB = 5,
30    Max1MB = 6,
31    Max4MB = 7,
32}
33
34impl BlockSize {
35    pub fn get_size(&self) -> usize {
36        match self {
37            BlockSize::Default | BlockSize::Max64KB => 64 * 1024,
38            BlockSize::Max256KB => 256 * 1024,
39            BlockSize::Max1MB => 1024 * 1024,
40            BlockSize::Max4MB => 4 * 1024 * 1024,
41        }
42    }
43}
44
45#[derive(Clone, Debug)]
46#[repr(u32)]
47pub enum BlockMode {
48    Linked = 0,
49    Independent,
50}
51
52#[derive(Clone, Debug)]
53#[repr(u32)]
54pub enum ContentChecksum {
55    NoChecksum = 0,
56    ChecksumEnabled,
57}
58
59#[derive(Clone, Debug)]
60#[repr(u32)]
61pub enum FrameType {
62    Frame = 0,
63    SkippableFrame,
64}
65
66#[derive(Clone, Debug)]
67#[repr(u32)]
68pub enum BlockChecksum {
69    NoBlockChecksum = 0,
70    BlockChecksumEnabled,
71}
72
73#[derive(Clone, Debug)]
74#[repr(C)]
75pub struct LZ4FFrameInfo {
76    pub block_size_id: BlockSize,
77    pub block_mode: BlockMode,
78    pub content_checksum_flag: ContentChecksum,
79    pub frame_type: FrameType,
80    pub content_size: c_ulonglong,
81    pub dict_id: c_uint,
82    pub block_checksum_flag: BlockChecksum,
83}
84
85#[derive(Clone, Debug)]
86#[repr(C)]
87pub struct LZ4FPreferences {
88    pub frame_info: LZ4FFrameInfo,
89    pub compression_level: c_uint,
90    pub auto_flush: c_uint,
91    pub favor_dec_speed: c_uint,
92    pub reserved: [c_uint; 3],
93}
94
95#[derive(Debug)]
96#[repr(C)]
97pub struct LZ4FCompressOptions {
98    pub stable_src: c_uint,
99    pub reserved: [c_uint; 3],
100}
101
102#[derive(Debug)]
103#[repr(C)]
104pub struct LZ4FDecompressOptions {
105    /// Matches the lz4-rs binding shape. Upstream uses this as a performance
106    /// pledge for linked-block history; this pure implementation keeps history
107    /// in owned context storage, so the flag is accepted but has no behavioral
108    /// effect.
109    pub stable_dst: c_uint,
110    /// When non-zero, block and content checksum fields are consumed but not
111    /// validated, matching upstream `LZ4F_decompressOptions_t`.
112    pub skipChecksums: c_uint,
113    pub reserved: [c_uint; 2],
114}
115
116#[derive(Debug)]
117#[repr(C)]
118pub struct LZ4StreamEncode {
119    _private: [u8; 0],
120}
121
122#[derive(Debug)]
123#[repr(C)]
124pub struct LZ4StreamDecode {
125    _private: [u8; 0],
126}
127
128#[derive(Debug)]
129#[repr(C)]
130pub struct LZ4StreamHC {
131    _private: [u8; 0],
132}
133
134#[derive(Debug)]
135#[repr(C)]
136pub struct LZ4FCDict {
137    _private: [u8; 0],
138}
139
140pub const LZ4F_VERSION: c_uint = 100;
141
142const LZ4_VERSION_NUMBER: c_int = 11000;
143const MINMATCH: usize = 4;
144const LZ4_HASH_BITS: usize = 12;
145const LZ4_HASH_BITS_U16: usize = LZ4_HASH_BITS + 1;
146const LZ4_64K_LIMIT: usize = 64 * 1024 + MFLIMIT - 1;
147const HASH_UNIT: usize = std::mem::size_of::<usize>();
148const LZ4HC_HASH_BITS: usize = 15;
149const LZ4HC_HASH_SIZE: usize = 1 << LZ4HC_HASH_BITS;
150const LZ4MID_HASH_BITS: usize = LZ4HC_HASH_BITS - 1;
151const LZ4MID_HASH_SIZE: usize = 1 << LZ4MID_HASH_BITS;
152const LZ4MID_HASHSIZE: usize = 8;
153const LZ4_OPT_NUM: usize = 1 << 12;
154const LZ4_DISTANCE_MAX: usize = 64 * 1024 - 1;
155const LAST_LITERALS: usize = 5;
156const MFLIMIT: usize = 12;
157const OPTIMAL_ML: usize = 18;
158const LZ4_MAX_INPUT_SIZE: c_int = 0x7E00_0000;
159const LZ4HC_CLEVEL_MIN: c_int = 3;
160const LZ4HC_CLEVEL_DEFAULT: c_int = 9;
161const LZ4HC_CLEVEL_MAX: c_int = 12;
162const LZ4F_MAGIC: [u8; 4] = [0x04, 0x22, 0x4D, 0x18];
163const LZ4F_SKIPPABLE_MAGIC_MIN: u32 = 0x184D_2A50;
164const LZ4F_SKIPPABLE_MAGIC_MAX: u32 = 0x184D_2A5F;
165const LZ4F_ERROR_MAX_CODE: usize = 24;
166const LZ4F_ERROR_GENERIC_CODE: usize = 1;
167const LZ4F_ERROR_MAX_BLOCK_SIZE_INVALID_CODE: usize = 2;
168const LZ4F_ERROR_BLOCK_MODE_INVALID_CODE: usize = 3;
169const LZ4F_ERROR_PARAMETER_INVALID_CODE: usize = 4;
170const LZ4F_ERROR_COMPRESSION_LEVEL_INVALID_CODE: usize = 5;
171const LZ4F_ERROR_HEADER_VERSION_WRONG_CODE: usize = 6;
172const LZ4F_ERROR_BLOCK_CHECKSUM_INVALID_CODE: usize = 7;
173const LZ4F_ERROR_RESERVED_FLAG_SET_CODE: usize = 8;
174const LZ4F_ERROR_ALLOCATION_FAILED_CODE: usize = 9;
175const LZ4F_ERROR_SRC_SIZE_TOO_LARGE_CODE: usize = 10;
176const LZ4F_ERROR_FRAME_SIZE_WRONG_CODE: usize = 14;
177const LZ4F_ERROR_FRAME_TYPE_UNKNOWN_CODE: usize = 13;
178const LZ4F_ERROR_SRC_PTR_WRONG_CODE: usize = 15;
179const LZ4F_ERROR_DECOMPRESSION_FAILED_CODE: usize = 16;
180const LZ4F_ERROR_HEADER_CHECKSUM_INVALID_CODE: usize = 17;
181const LZ4F_ERROR_DST_MAX_SIZE_TOO_SMALL_CODE: usize = 11;
182const LZ4F_ERROR_FRAME_HEADER_INCOMPLETE_CODE: usize = 12;
183const LZ4F_ERROR_CONTENT_CHECKSUM_INVALID_CODE: usize = 18;
184const LZ4F_ERROR_FRAME_DECODING_ALREADY_STARTED_CODE: usize = 19;
185const LZ4F_ERROR_COMPRESSION_STATE_UNINITIALIZED_CODE: usize = 20;
186const LZ4F_ERROR_PARAMETER_NULL_CODE: usize = 21;
187const LZ4F_ERROR_IO_WRITE_CODE: usize = 22;
188const LZ4F_ERROR_IO_READ_CODE: usize = 23;
189const ERROR_GENERIC: usize = lz4f_error(LZ4F_ERROR_GENERIC_CODE);
190const ERROR_MAX_BLOCK_SIZE_INVALID: usize = lz4f_error(LZ4F_ERROR_MAX_BLOCK_SIZE_INVALID_CODE);
191const ERROR_BLOCK_MODE_INVALID: usize = lz4f_error(LZ4F_ERROR_BLOCK_MODE_INVALID_CODE);
192const ERROR_PARAMETER_INVALID: usize = lz4f_error(LZ4F_ERROR_PARAMETER_INVALID_CODE);
193const ERROR_COMPRESSION_LEVEL_INVALID: usize =
194    lz4f_error(LZ4F_ERROR_COMPRESSION_LEVEL_INVALID_CODE);
195const ERROR_HEADER_VERSION_WRONG: usize = lz4f_error(LZ4F_ERROR_HEADER_VERSION_WRONG_CODE);
196const ERROR_BLOCK_CHECKSUM_INVALID: usize = lz4f_error(LZ4F_ERROR_BLOCK_CHECKSUM_INVALID_CODE);
197const ERROR_RESERVED_FLAG_SET: usize = lz4f_error(LZ4F_ERROR_RESERVED_FLAG_SET_CODE);
198const ERROR_ALLOCATION_FAILED: usize = lz4f_error(LZ4F_ERROR_ALLOCATION_FAILED_CODE);
199const ERROR_SRC_SIZE_TOO_LARGE: usize = lz4f_error(LZ4F_ERROR_SRC_SIZE_TOO_LARGE_CODE);
200const ERROR_FRAME_SIZE_WRONG: usize = lz4f_error(LZ4F_ERROR_FRAME_SIZE_WRONG_CODE);
201const ERROR_FRAME_TYPE_UNKNOWN: usize = lz4f_error(LZ4F_ERROR_FRAME_TYPE_UNKNOWN_CODE);
202const ERROR_SRC_PTR_WRONG: usize = lz4f_error(LZ4F_ERROR_SRC_PTR_WRONG_CODE);
203const ERROR_DECOMPRESSION_FAILED: usize = lz4f_error(LZ4F_ERROR_DECOMPRESSION_FAILED_CODE);
204const ERROR_HEADER_CHECKSUM_INVALID: usize = lz4f_error(LZ4F_ERROR_HEADER_CHECKSUM_INVALID_CODE);
205const ERROR_DST_TOO_SMALL: usize = lz4f_error(LZ4F_ERROR_DST_MAX_SIZE_TOO_SMALL_CODE);
206const ERROR_BAD_HEADER: usize = lz4f_error(LZ4F_ERROR_FRAME_HEADER_INCOMPLETE_CODE);
207const ERROR_CHECKSUM_INVALID: usize = lz4f_error(LZ4F_ERROR_CONTENT_CHECKSUM_INVALID_CODE);
208const ERROR_FRAME_DECODING_ALREADY_STARTED: usize =
209    lz4f_error(LZ4F_ERROR_FRAME_DECODING_ALREADY_STARTED_CODE);
210const ERROR_COMPRESSION_STATE_UNINITIALIZED: usize =
211    lz4f_error(LZ4F_ERROR_COMPRESSION_STATE_UNINITIALIZED_CODE);
212const ERROR_PARAMETER_NULL: usize = lz4f_error(LZ4F_ERROR_PARAMETER_NULL_CODE);
213const ERROR_IO_WRITE: usize = lz4f_error(LZ4F_ERROR_IO_WRITE_CODE);
214const ERROR_IO_READ: usize = lz4f_error(LZ4F_ERROR_IO_READ_CODE);
215
216static ERROR_GENERIC_NAME: &[u8] = b"ERROR_GENERIC\0";
217static ERROR_MAX_BLOCK_SIZE_NAME: &[u8] = b"ERROR_maxBlockSize_invalid\0";
218static ERROR_BLOCK_MODE_NAME: &[u8] = b"ERROR_blockMode_invalid\0";
219static ERROR_PARAMETER_INVALID_NAME: &[u8] = b"ERROR_parameter_invalid\0";
220static ERROR_COMPRESSION_LEVEL_NAME: &[u8] = b"ERROR_compressionLevel_invalid\0";
221static ERROR_HEADER_VERSION_NAME: &[u8] = b"ERROR_headerVersion_wrong\0";
222static ERROR_BLOCK_CHECKSUM_NAME: &[u8] = b"ERROR_blockChecksum_invalid\0";
223static ERROR_RESERVED_FLAG_NAME: &[u8] = b"ERROR_reservedFlag_set\0";
224static ERROR_ALLOCATION_FAILED_NAME: &[u8] = b"ERROR_allocation_failed\0";
225static ERROR_SRC_SIZE_TOO_LARGE_NAME: &[u8] = b"ERROR_srcSize_tooLarge\0";
226static ERROR_FRAME_SIZE_NAME: &[u8] = b"ERROR_frameSize_wrong\0";
227static ERROR_FRAME_TYPE_NAME: &[u8] = b"ERROR_frameType_unknown\0";
228static ERROR_SRC_PTR_NAME: &[u8] = b"ERROR_srcPtr_wrong\0";
229static ERROR_DECOMPRESSION_FAILED_NAME: &[u8] = b"ERROR_decompressionFailed\0";
230static ERROR_HEADER_CHECKSUM_NAME: &[u8] = b"ERROR_headerChecksum_invalid\0";
231static ERROR_DST_NAME: &[u8] = b"ERROR_dstMaxSize_tooSmall\0";
232static ERROR_BAD_HEADER_NAME: &[u8] = b"ERROR_frameHeader_incomplete\0";
233static ERROR_CHECKSUM_NAME: &[u8] = b"ERROR_contentChecksum_invalid\0";
234static ERROR_FRAME_DECODING_ALREADY_STARTED_NAME: &[u8] = b"ERROR_frameDecoding_alreadyStarted\0";
235static ERROR_COMPRESSION_STATE_UNINITIALIZED_NAME: &[u8] =
236    b"ERROR_compressionState_uninitialized\0";
237static ERROR_PARAMETER_NULL_NAME: &[u8] = b"ERROR_parameter_null\0";
238static ERROR_IO_WRITE_NAME: &[u8] = b"ERROR_io_write\0";
239static ERROR_IO_READ_NAME: &[u8] = b"ERROR_io_read\0";
240static ERROR_UNSPECIFIED_NAME: &[u8] = b"Unspecified error code\0";
241static LZ4_VERSION_STRING_BYTES: &[u8] = b"1.10.0\0";
242
243const fn lz4f_error(code: usize) -> usize {
244    usize::MAX - (code - 1)
245}
246
247#[derive(Debug)]
248struct CompressionCtx {
249    prefs: FramePrefs,
250    content_hasher: XxHash32,
251    dictionary: Vec<u8>,
252    pending: Vec<u8>,
253    total_input: u64,
254    external_dictionary: bool,
255    started: bool,
256}
257
258#[derive(Clone, Copy, Debug)]
259struct FramePrefs {
260    block_size_id: u8,
261    block_independent: bool,
262    block_checksum: bool,
263    content_checksum: bool,
264    content_size: u64,
265    dict_id: c_uint,
266    compression_level: c_int,
267    auto_flush: bool,
268    favor_dec_speed: bool,
269}
270
271impl Default for FramePrefs {
272    fn default() -> Self {
273        // Matches upstream `LZ4F_INIT_FRAMEINFO` (lz4frame.h:186): a NULL
274        // `LZ4F_preferences_t*` and a zero-initialised one both decode to
275        // `LZ4F_blockLinked = 0`, not Independent.
276        Self {
277            block_size_id: 4,
278            block_independent: false,
279            block_checksum: false,
280            content_checksum: false,
281            content_size: 0,
282            dict_id: 0,
283            compression_level: 0,
284            auto_flush: false,
285            favor_dec_speed: false,
286        }
287    }
288}
289
290#[derive(Debug)]
291struct DecompressionCtx {
292    input: Vec<u8>,
293    pending: Vec<u8>,
294    pending_pos: usize,
295    pos: usize,
296    parsed_header: bool,
297    done: bool,
298    block_checksum: bool,
299    content_checksum: bool,
300    content_size: u64,
301    content_read: u64,
302    dict_id: c_uint,
303    block_independent: bool,
304    block_max: usize,
305    dictionary: Vec<u8>,
306    external_dictionary: bool,
307    content_hasher: XxHash32,
308    raw_block_remaining: usize,
309    raw_block_checksum: XxHash32,
310    skip_checksums: bool,
311}
312
313#[derive(Debug)]
314struct HcStreamCtx {
315    compression_level: c_int,
316    dictionary: Vec<u8>,
317    attached_dictionary: bool,
318    favor_dec_speed: bool,
319}
320
321#[derive(Debug, Default)]
322struct EncodeStreamCtx {
323    dictionary: Vec<u8>,
324    attached_dictionary: bool,
325}
326
327#[derive(Debug, Default)]
328struct DecodeStreamCtx {
329    dictionary: Vec<u8>,
330}
331
332#[derive(Debug, Default)]
333struct CDictCtx {
334    dictionary: Vec<u8>,
335}
336
337impl Default for HcStreamCtx {
338    fn default() -> Self {
339        Self {
340            compression_level: LZ4HC_CLEVEL_DEFAULT,
341            dictionary: Vec::new(),
342            attached_dictionary: false,
343            favor_dec_speed: false,
344        }
345    }
346}
347
348impl Default for DecompressionCtx {
349    fn default() -> Self {
350        Self {
351            input: Vec::new(),
352            pending: Vec::new(),
353            pending_pos: 0,
354            pos: 0,
355            parsed_header: false,
356            done: false,
357            block_checksum: false,
358            content_checksum: false,
359            content_size: 0,
360            content_read: 0,
361            dict_id: 0,
362            block_independent: true,
363            block_max: 64 * 1024,
364            dictionary: Vec::new(),
365            external_dictionary: false,
366            content_hasher: XxHash32::new(0),
367            raw_block_remaining: 0,
368            raw_block_checksum: XxHash32::new(0),
369            skip_checksums: false,
370        }
371    }
372}
373
374/// Library version number; useful to check dll version. Requires v1.3.0+.
375#[no_mangle]
376pub unsafe extern "C" fn LZ4_versionNumber() -> c_int {
377    LZ4_VERSION_NUMBER
378}
379
380/// Library version string; useful to check dll version. Requires v1.7.5+.
381#[no_mangle]
382pub extern "C" fn LZ4_versionString() -> *const c_char {
383    LZ4_VERSION_STRING_BYTES.as_ptr() as *const c_char
384}
385
386/// `LZ4_compressBound()` :
387/// Provides the maximum size that LZ4 compression may output in a "worst case" scenario
388/// (input data not compressible). This function is primarily useful for memory allocation
389/// purposes (destination buffer size). Note that `LZ4_compress_default()` compresses faster
390/// when `dstCapacity` is `>= LZ4_compressBound(srcSize)`.
391///
392/// - `inputSize` : max supported value is `LZ4_MAX_INPUT_SIZE`
393/// - return : maximum output size in a "worst case" scenario,
394///   or 0 if input size is incorrect (too large or negative)
395#[no_mangle]
396pub unsafe extern "C" fn LZ4_compressBound(size: c_int) -> c_int {
397    if !(0..=LZ4_MAX_INPUT_SIZE).contains(&size) {
398        0
399    } else {
400        size + (size / 255) + 16
401    }
402}
403
404/// `LZ4_compress_default()` :
405/// Compresses `srcSize` bytes from buffer `src` into already-allocated `dst` buffer of size
406/// `dstCapacity`. Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)`.
407/// It also runs faster, so it's a recommended setting.
408/// If the function cannot compress `src` into a more limited `dst` budget, compression stops
409/// *immediately*, and the function result is zero. In which case, `dst` content is undefined (invalid).
410///
411/// - `srcSize` : max supported value is `LZ4_MAX_INPUT_SIZE`.
412/// - `dstCapacity` : size of buffer `dst` (which must be already allocated).
413/// - return : the number of bytes written into buffer `dst` (necessarily `<= dstCapacity`)
414///   or 0 if compression fails.
415///
416/// Note : This function is protected against buffer overflow scenarios
417/// (never writes outside `dst` buffer, nor reads outside `source` buffer).
418#[no_mangle]
419pub unsafe extern "C" fn LZ4_compress_default(
420    source: *const c_char,
421    dest: *mut c_char,
422    sourceSize: c_int,
423    maxDestSize: c_int,
424) -> c_int {
425    LZ4_compress_fast(source, dest, sourceSize, maxDestSize, 1)
426}
427
428/// Obsolete compression function (since v1.7.3). Use `LZ4_compress_default()` instead.
429#[no_mangle]
430pub unsafe extern "C" fn LZ4_compress(
431    source: *const c_char,
432    dest: *mut c_char,
433    sourceSize: c_int,
434) -> c_int {
435    LZ4_compress_default(source, dest, sourceSize, LZ4_compressBound(sourceSize))
436}
437
438/// Obsolete compression function (since v1.7.3). Use `LZ4_compress_default()` instead.
439#[no_mangle]
440pub unsafe extern "C" fn LZ4_compress_limitedOutput(
441    source: *const c_char,
442    dest: *mut c_char,
443    sourceSize: c_int,
444    maxOutputSize: c_int,
445) -> c_int {
446    LZ4_compress_default(source, dest, sourceSize, maxOutputSize)
447}
448
449/// Obsolete compression function (since v1.7.3). Use `LZ4_compress_fast_extState()` instead.
450#[no_mangle]
451pub unsafe extern "C" fn LZ4_compress_withState(
452    state: *mut c_void,
453    source: *const c_char,
454    dest: *mut c_char,
455    inputSize: c_int,
456) -> c_int {
457    LZ4_compress_fast_extState(
458        state,
459        source,
460        dest,
461        inputSize,
462        LZ4_compressBound(inputSize),
463        1,
464    )
465}
466
467/// Obsolete compression function (since v1.7.3). Use `LZ4_compress_fast_extState()` instead.
468#[no_mangle]
469pub unsafe extern "C" fn LZ4_compress_limitedOutput_withState(
470    state: *mut c_void,
471    source: *const c_char,
472    dest: *mut c_char,
473    inputSize: c_int,
474    maxOutputSize: c_int,
475) -> c_int {
476    LZ4_compress_fast_extState(state, source, dest, inputSize, maxOutputSize, 1)
477}
478
479/// `LZ4_compress_fast()` :
480/// Same as `LZ4_compress_default()`, but allows selection of "acceleration" factor.
481/// The larger the acceleration value, the faster the algorithm, but also the lesser the
482/// compression. It's a trade-off. It can be fine tuned, with each successive value providing
483/// roughly +~3% to speed. An acceleration value of "1" is the same as regular
484/// `LZ4_compress_default()`. Values `<= 0` will be replaced by `LZ4_ACCELERATION_DEFAULT`
485/// (currently == 1). Values `> LZ4_ACCELERATION_MAX` will be replaced by `LZ4_ACCELERATION_MAX`
486/// (currently == 65537).
487#[no_mangle]
488pub unsafe extern "C" fn LZ4_compress_fast(
489    source: *const c_char,
490    dest: *mut c_char,
491    sourceSize: c_int,
492    maxDestSize: c_int,
493    acceleration: c_int,
494) -> c_int {
495    if !(0..=LZ4_MAX_INPUT_SIZE).contains(&sourceSize)
496        || maxDestSize <= 0
497        || (sourceSize > 0 && source.is_null())
498        || dest.is_null()
499    {
500        return 0;
501    }
502    let src = if sourceSize == 0 {
503        &[]
504    } else {
505        slice::from_raw_parts(source as *const u8, sourceSize as usize)
506    };
507    let dst = slice::from_raw_parts_mut(dest as *mut u8, maxDestSize as usize);
508    compress_block(src, dst, normalize_acceleration(acceleration)).map_or(0, |n| n as c_int)
509}
510
511/// Returns the size in bytes required for an `LZ4_compress_fast_extState()` state buffer.
512/// Allocate on 8-byte boundaries (a normal `malloc()` is sufficient).
513#[no_mangle]
514pub extern "C" fn LZ4_sizeofState() -> c_int {
515    ((1usize << (LZ4_HASH_BITS + 2)) + 32) as c_int
516}
517
518/// `LZ4_compress_fast_extState()` :
519/// Same as `LZ4_compress_fast()`, using an externally allocated memory space for its state.
520/// Use `LZ4_sizeofState()` to know how much memory must be allocated, and allocate it on
521/// 8-byte boundaries (using `malloc()` typically). Then, provide this buffer as `state` to
522/// the compression function.
523#[no_mangle]
524pub unsafe extern "C" fn LZ4_compress_fast_extState(
525    state: *mut c_void,
526    source: *const c_char,
527    dest: *mut c_char,
528    sourceSize: c_int,
529    maxDestSize: c_int,
530    acceleration: c_int,
531) -> c_int {
532    if !(0..=LZ4_MAX_INPUT_SIZE).contains(&sourceSize)
533        || maxDestSize <= 0
534        || (sourceSize > 0 && source.is_null())
535        || dest.is_null()
536        || state.is_null()
537    {
538        return 0;
539    }
540    let src = if sourceSize == 0 {
541        &[]
542    } else {
543        slice::from_raw_parts(source as *const u8, sourceSize as usize)
544    };
545    let dst = slice::from_raw_parts_mut(dest as *mut u8, maxDestSize as usize);
546    compress_block_ext_state(state, src, dst, normalize_acceleration(acceleration))
547        .map_or(0, |n| n as c_int)
548}
549
550/// `LZ4_compress_fast_extState_fastReset()` :
551/// A variant of `LZ4_compress_fast_extState()`.
552///
553/// Using this variant avoids an expensive initialization step. It is only safe to call if
554/// the state buffer is known to be correctly initialized already (see `LZ4_resetStream_fast()`
555/// for a definition of "correctly initialized"). From a high level, the difference is that
556/// this function initializes the provided state with a call to something like
557/// `LZ4_resetStream_fast()` while `LZ4_compress_fast_extState()` starts with a call to
558/// `LZ4_resetStream()`.
559#[no_mangle]
560pub unsafe extern "C" fn LZ4_compress_fast_extState_fastReset(
561    state: *mut c_void,
562    source: *const c_char,
563    dest: *mut c_char,
564    sourceSize: c_int,
565    maxDestSize: c_int,
566    acceleration: c_int,
567) -> c_int {
568    LZ4_compress_fast_extState(state, source, dest, sourceSize, maxDestSize, acceleration)
569}
570
571/// `LZ4_compress_destSize()` :
572/// Reverse the logic: compresses as much data as possible from `src` buffer into already
573/// allocated buffer `dst`, of size `>= dstCapacity`. This function either compresses the
574/// entire `src` content into `dst` if it's large enough, or fills `dst` buffer completely
575/// with as much data as possible from `src`. Note: acceleration parameter is fixed to "default".
576///
577/// - `*srcSizePtr` : in+out parameter. Initially contains size of input. Will be modified to
578///   indicate how many bytes were read from `src` to fill `dst`. New value is
579///   necessarily `<=` input value.
580/// - return : Nb bytes written into `dst` (necessarily `<= dstCapacity`) or 0 if compression
581///   fails.
582///
583/// Note : `targetDstSize` must be `>= 1`, because it's the smallest valid lz4 payload.
584///
585/// Note 2 : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): the produced
586///          compressed content could, in rare circumstances, require to be decompressed into
587///          a destination buffer larger by at least 1 byte than `decompressedSize`.
588#[no_mangle]
589pub unsafe extern "C" fn LZ4_compress_destSize(
590    src: *const c_char,
591    dst: *mut c_char,
592    srcSizePtr: *mut c_int,
593    targetDstSize: c_int,
594) -> c_int {
595    if src.is_null()
596        || dst.is_null()
597        || srcSizePtr.is_null()
598        || *srcSizePtr < 0
599        || targetDstSize <= 0
600    {
601        return 0;
602    }
603    let src_slice = slice::from_raw_parts(src as *const u8, *srcSizePtr as usize);
604    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, targetDstSize as usize);
605    let Some((consumed, written)) = compress_dest_size(src_slice, dst_slice, 1) else {
606        return 0;
607    };
608    *srcSizePtr = consumed as c_int;
609    written as c_int
610}
611
612/// `LZ4_compress_destSize_extState()` : introduced in v1.10.0.
613/// Same as `LZ4_compress_destSize()`, but using an externally allocated state. Also exposes
614/// `acceleration`.
615#[no_mangle]
616pub unsafe extern "C" fn LZ4_compress_destSize_extState(
617    state: *mut c_void,
618    src: *const c_char,
619    dst: *mut c_char,
620    srcSizePtr: *mut c_int,
621    targetDstSize: c_int,
622    acceleration: c_int,
623) -> c_int {
624    if state.is_null()
625        || src.is_null()
626        || dst.is_null()
627        || srcSizePtr.is_null()
628        || *srcSizePtr < 0
629        || targetDstSize <= 0
630    {
631        return 0;
632    }
633    if targetDstSize >= LZ4_compressBound(*srcSizePtr) {
634        return LZ4_compress_fast_extState(
635            state,
636            src,
637            dst,
638            *srcSizePtr,
639            targetDstSize,
640            acceleration,
641        );
642    }
643
644    let src_slice = slice::from_raw_parts(src as *const u8, *srcSizePtr as usize);
645    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, targetDstSize as usize);
646    let Some((consumed, written)) =
647        compress_dest_size(src_slice, dst_slice, normalize_acceleration(acceleration))
648    else {
649        return 0;
650    };
651    *srcSizePtr = consumed as c_int;
652    written as c_int
653}
654
655/// `LZ4_compress_HC()` :
656/// Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm.
657/// `dst` must be already allocated. Compression is guaranteed to succeed if
658/// `dstCapacity >= LZ4_compressBound(srcSize)`. Max supported `srcSize` value is
659/// `LZ4_MAX_INPUT_SIZE`. `compressionLevel` : any value between 1 and `LZ4HC_CLEVEL_MAX`
660/// will work. Values `> LZ4HC_CLEVEL_MAX` behave the same as `LZ4HC_CLEVEL_MAX`.
661///
662/// - return : the number of bytes written into `dst` or 0 if compression fails.
663#[no_mangle]
664pub unsafe extern "C" fn LZ4_compress_HC(
665    src: *const c_char,
666    dst: *mut c_char,
667    srcSize: c_int,
668    dstCapacity: c_int,
669    compressionLevel: c_int,
670) -> c_int {
671    if srcSize < 0 || dstCapacity <= 0 || src.is_null() || dst.is_null() {
672        return 0;
673    }
674    let src = slice::from_raw_parts(src as *const u8, srcSize as usize);
675    let dst = slice::from_raw_parts_mut(dst as *mut u8, dstCapacity as usize);
676    compress_block_hc(src, dst, compressionLevel, false).map_or(0, |n| n as c_int)
677}
678
679/// Returns the size in bytes required for an `LZ4_compress_HC_extStateHC()` state buffer.
680/// Memory segment must be aligned on 8-byte boundaries.
681#[no_mangle]
682pub extern "C" fn LZ4_sizeofStateHC() -> c_int {
683    cmp::max(
684        std::mem::size_of::<HcStreamCtx>(),
685        std::mem::align_of::<HcStreamCtx>(),
686    ) as c_int
687}
688
689/// `LZ4_compress_HC_extStateHC()` :
690/// Same as `LZ4_compress_HC()`, but using an externally allocated memory segment for `state`.
691/// `state` size is provided by `LZ4_sizeofStateHC()`. Memory segment must be aligned on
692/// 8-byte boundaries (which a normal `malloc()` should do properly).
693#[no_mangle]
694pub unsafe extern "C" fn LZ4_compress_HC_extStateHC(
695    stateHC: *mut c_void,
696    src: *const c_char,
697    dst: *mut c_char,
698    srcSize: c_int,
699    maxDstSize: c_int,
700    compressionLevel: c_int,
701) -> c_int {
702    if stateHC.is_null() || srcSize < 0 || maxDstSize <= 0 || src.is_null() || dst.is_null() {
703        return 0;
704    }
705    let ctx = stateHC as *mut HcStreamCtx;
706    ptr::write(ctx, HcStreamCtx::default());
707    (*ctx).compression_level = normalize_hc_level(compressionLevel);
708    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
709    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, maxDstSize as usize);
710    compress_hc_stream_block(
711        &mut *ctx,
712        src_slice,
713        dst_slice,
714        normalize_hc_level(compressionLevel),
715        false,
716    )
717}
718
719/// `LZ4_compress_HC_extStateHC_fastReset()` :
720/// A variant of `LZ4_compress_HC_extStateHC()`.
721///
722/// Using this variant avoids an expensive initialization step. It is only safe to call if
723/// the state buffer is known to be correctly initialized already. The difference is that
724/// this function initializes the provided state with a call to `LZ4_resetStreamHC_fast()`
725/// while `LZ4_compress_HC_extStateHC()` starts with a call to `LZ4_resetStreamHC()`.
726#[no_mangle]
727pub unsafe extern "C" fn LZ4_compress_HC_extStateHC_fastReset(
728    state: *mut c_void,
729    src: *const c_char,
730    dst: *mut c_char,
731    srcSize: c_int,
732    dstCapacity: c_int,
733    compressionLevel: c_int,
734) -> c_int {
735    if state.is_null() || srcSize < 0 || dstCapacity <= 0 || src.is_null() || dst.is_null() {
736        return 0;
737    }
738    let ctx = &mut *(state as *mut HcStreamCtx);
739    ctx.compression_level = normalize_hc_level(compressionLevel);
740    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
741    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, dstCapacity as usize);
742    compress_hc_stream_block(
743        ctx,
744        src_slice,
745        dst_slice,
746        normalize_hc_level(compressionLevel),
747        ctx.favor_dec_speed,
748    )
749}
750
751/// `LZ4_compress_HC_destSize()` : v1.9.0+
752/// Will compress as much data as possible from `src` to fit into `targetDstSize` budget.
753/// Result is provided in 2 parts :
754/// - return : the number of bytes written into `dst` (necessarily `<= targetDstSize`) or 0
755///   if compression fails.
756/// - `*srcSizePtr` : on success, updated to indicate how many bytes were read from `src`.
757#[no_mangle]
758pub unsafe extern "C" fn LZ4_compress_HC_destSize(
759    stateHC: *mut c_void,
760    src: *const c_char,
761    dst: *mut c_char,
762    srcSizePtr: *mut c_int,
763    targetDstSize: c_int,
764    compressionLevel: c_int,
765) -> c_int {
766    if stateHC.is_null()
767        || src.is_null()
768        || dst.is_null()
769        || srcSizePtr.is_null()
770        || *srcSizePtr < 0
771        || targetDstSize <= 0
772    {
773        return 0;
774    }
775
776    let src_len = *srcSizePtr as usize;
777    let src_slice = slice::from_raw_parts(src as *const u8, src_len);
778    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, targetDstSize as usize);
779    let Some((consumed, written)) =
780        compress_hc_dest_size(src_slice, dst_slice, compressionLevel, false)
781    else {
782        return 0;
783    };
784    *srcSizePtr = consumed as c_int;
785    written as c_int
786}
787
788/// Deprecated HC compression function. Use `LZ4_compress_HC()` instead.
789#[no_mangle]
790pub unsafe extern "C" fn LZ4_compressHC(
791    src: *const c_char,
792    dst: *mut c_char,
793    srcSize: c_int,
794) -> c_int {
795    LZ4_compress_HC(src, dst, srcSize, LZ4_compressBound(srcSize), 0)
796}
797
798/// Deprecated HC compression function. Use `LZ4_compress_HC()` instead.
799#[no_mangle]
800pub unsafe extern "C" fn LZ4_compressHC_limitedOutput(
801    src: *const c_char,
802    dst: *mut c_char,
803    srcSize: c_int,
804    maxDstSize: c_int,
805) -> c_int {
806    LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0)
807}
808
809/// Deprecated HC compression function. Use `LZ4_compress_HC()` instead.
810#[no_mangle]
811pub unsafe extern "C" fn LZ4_compressHC2(
812    src: *const c_char,
813    dst: *mut c_char,
814    srcSize: c_int,
815    cLevel: c_int,
816) -> c_int {
817    LZ4_compress_HC(src, dst, srcSize, LZ4_compressBound(srcSize), cLevel)
818}
819
820/// Deprecated HC compression function. Use `LZ4_compress_HC()` instead.
821#[no_mangle]
822pub unsafe extern "C" fn LZ4_compressHC2_limitedOutput(
823    src: *const c_char,
824    dst: *mut c_char,
825    srcSize: c_int,
826    maxDstSize: c_int,
827    cLevel: c_int,
828) -> c_int {
829    LZ4_compress_HC(src, dst, srcSize, maxDstSize, cLevel)
830}
831
832/// Deprecated HC compression function. Use `LZ4_compress_HC_extStateHC()` instead.
833#[no_mangle]
834pub unsafe extern "C" fn LZ4_compressHC_withStateHC(
835    state: *mut c_void,
836    src: *const c_char,
837    dst: *mut c_char,
838    srcSize: c_int,
839) -> c_int {
840    LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), 0)
841}
842
843/// Deprecated HC compression function. Use `LZ4_compress_HC_extStateHC()` instead.
844#[no_mangle]
845pub unsafe extern "C" fn LZ4_compressHC_limitedOutput_withStateHC(
846    state: *mut c_void,
847    src: *const c_char,
848    dst: *mut c_char,
849    srcSize: c_int,
850    maxDstSize: c_int,
851) -> c_int {
852    LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, 0)
853}
854
855/// Deprecated HC compression function. Use `LZ4_compress_HC_extStateHC()` instead.
856#[no_mangle]
857pub unsafe extern "C" fn LZ4_compressHC2_withStateHC(
858    state: *mut c_void,
859    src: *const c_char,
860    dst: *mut c_char,
861    srcSize: c_int,
862    cLevel: c_int,
863) -> c_int {
864    LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), cLevel)
865}
866
867/// Deprecated HC compression function. Use `LZ4_compress_HC_extStateHC()` instead.
868#[no_mangle]
869pub unsafe extern "C" fn LZ4_compressHC2_limitedOutput_withStateHC(
870    state: *mut c_void,
871    src: *const c_char,
872    dst: *mut c_char,
873    srcSize: c_int,
874    maxDstSize: c_int,
875    cLevel: c_int,
876) -> c_int {
877    LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, cLevel)
878}
879
880/// `LZ4_decompress_safe()` :
881/// - `compressedSize` : is the exact complete size of the compressed block.
882/// - `dstCapacity` : is the size of destination buffer (which must be already allocated),
883///   presumed an upper bound of decompressed size.
884/// - return : the number of bytes decompressed into destination buffer (necessarily
885///   `<= dstCapacity`). If destination buffer is not large enough, decoding will
886///   stop and output an error code (negative value). If the source stream is
887///   detected malformed, the function will stop decoding and return a negative result.
888///
889/// Note 1 : This function is protected against malicious data packets: it will never write
890/// outside `dst` buffer, nor read outside `source` buffer, even if the compressed block is
891/// maliciously modified to order the decoder to do these actions. In such case, the decoder
892/// stops immediately, and considers the compressed block malformed.
893///
894/// Note 2 : `compressedSize` and `dstCapacity` must be provided to the function, the
895/// compressed block does not contain them.
896#[no_mangle]
897pub unsafe extern "C" fn LZ4_decompress_safe(
898    source: *const c_char,
899    dest: *mut c_char,
900    compressedSize: c_int,
901    maxDecompressedSize: c_int,
902) -> c_int {
903    if compressedSize < 0 || maxDecompressedSize < 0 || source.is_null() || dest.is_null() {
904        return -1;
905    }
906    let src = slice::from_raw_parts(source as *const u8, compressedSize as usize);
907    let dst = slice::from_raw_parts_mut(dest as *mut u8, maxDecompressedSize as usize);
908    decompress_block(src, dst).map_or(-1, |n| n as c_int)
909}
910
911/// `LZ4_decompress_safe_usingDict()` :
912/// Works the same as a combination of `LZ4_setStreamDecode()` followed by
913/// `LZ4_decompress_safe_continue()`. However, it's stateless: it doesn't need any
914/// `LZ4_streamDecode_t` state. Dictionary is presumed stable: it must remain accessible
915/// and unmodified during decompression.
916///
917/// Performance tip : Decompression speed can be substantially increased when
918/// `dst == dictStart + dictSize`.
919#[no_mangle]
920pub unsafe extern "C" fn LZ4_decompress_safe_usingDict(
921    source: *const c_char,
922    dest: *mut c_char,
923    compressedSize: c_int,
924    maxDecompressedSize: c_int,
925    dictStart: *const c_char,
926    dictSize: c_int,
927) -> c_int {
928    if compressedSize < 0
929        || maxDecompressedSize < 0
930        || dictSize < 0
931        || source.is_null()
932        || dest.is_null()
933        || (dictSize > 0 && dictStart.is_null())
934    {
935        return -1;
936    }
937
938    // Mirrors upstream `LZ4_decompress_safe_usingDict` (lz4.c:2715-2727):
939    // dispatch by dict layout to the corresponding specialised decoder.
940    if dictSize == 0 {
941        return LZ4_decompress_safe(source, dest, compressedSize, maxDecompressedSize);
942    }
943    let src = slice::from_raw_parts(source as *const u8, compressedSize as usize);
944    let dst = slice::from_raw_parts_mut(dest as *mut u8, maxDecompressedSize as usize);
945    let dict = slice::from_raw_parts(dictStart as *const u8, dictSize as usize);
946
947    let dict_end = (dictStart as *const u8).add(dictSize as usize);
948    if dict_end == dest as *const u8 {
949        // Contiguous prefix (`dictStart + dictSize == dest`). Upstream
950        // routes to `LZ4_decompress_safe_withPrefix64k` when
951        // `dictSize >= 64 KB - 1` and to `LZ4_decompress_safe_withSmallPrefix`
952        // otherwise; both rely on the prefix bytes lying immediately before
953        // `dest` in memory and read them via implicit pointer arithmetic.
954        // The Rust ext-dict decoder reads the same bytes through an explicit
955        // dict slice, producing byte-equivalent output, so both branches map
956        // to the same call here. (A future pass could specialise these with
957        // unsafe pointer reads from `dst.as_ptr().sub(prefix_size)` if the
958        // benchmark ever shows a measurable gap.)
959        let _is_prefix_64k = dictSize as usize >= 64 * 1024 - 1;
960        return decompress_block_with_dict(src, dst, dict).map_or(-1, |n| n as c_int);
961    }
962
963    // forceExtDict path: dictionary lives in a separate buffer.
964    decompress_block_with_dict(src, dst, dict).map_or(-1, |n| n as c_int)
965}
966
967/// `LZ4_decompress_safe_partial()` :
968/// Decompress an LZ4 compressed block, of size `srcSize` at position `src`, into destination
969/// buffer `dst` of size `dstCapacity`. Up to `targetOutputSize` bytes will be decoded. The
970/// function stops decoding on reaching this objective. This can be useful to boost performance
971/// whenever only the beginning of a block is required.
972///
973/// - return : the number of bytes decoded in `dst` (necessarily `<= targetOutputSize`). If
974///   source stream is detected malformed, function returns a negative result.
975///
976/// Note 1 : return can be `< targetOutputSize`, if compressed block contains less data.
977/// Note 2 : `targetOutputSize` must be `<= dstCapacity`.
978/// Note 3 : If `srcSize` is the exact size of the block, then `targetOutputSize` can be any
979///          value, including larger than the block's decompressed size. The function will,
980///          at most, generate block's decompressed size.
981/// Note 4 : If `srcSize` is _larger_ than block's compressed size, then `targetOutputSize`
982///          **MUST** be `<=` block's decompressed size. Otherwise, *silent corruption will occur*.
983#[no_mangle]
984pub unsafe extern "C" fn LZ4_decompress_safe_partial(
985    source: *const c_char,
986    dest: *mut c_char,
987    compressedSize: c_int,
988    targetOutputSize: c_int,
989    dstCapacity: c_int,
990) -> c_int {
991    if compressedSize < 0
992        || targetOutputSize < 0
993        || dstCapacity < 0
994        || source.is_null()
995        || dest.is_null()
996    {
997        return -1;
998    }
999    let src = slice::from_raw_parts(source as *const u8, compressedSize as usize);
1000    let dst = slice::from_raw_parts_mut(dest as *mut u8, dstCapacity as usize);
1001    let target = cmp::min(targetOutputSize as usize, dst.len());
1002    decompress_block_partial(src, dst, target).map_or(-1, |n| n as c_int)
1003}
1004
1005/// `LZ4_decompress_safe_partial_usingDict()` :
1006/// Behaves the same as `LZ4_decompress_safe_partial()` with the added ability to specify a
1007/// memory segment for past data.
1008///
1009/// Performance tip : Decompression speed can be substantially increased when
1010/// `dst == dictStart + dictSize`.
1011#[no_mangle]
1012pub unsafe extern "C" fn LZ4_decompress_safe_partial_usingDict(
1013    source: *const c_char,
1014    dest: *mut c_char,
1015    compressedSize: c_int,
1016    targetOutputSize: c_int,
1017    dstCapacity: c_int,
1018    dictStart: *const c_char,
1019    dictSize: c_int,
1020) -> c_int {
1021    if compressedSize < 0
1022        || targetOutputSize < 0
1023        || dstCapacity < 0
1024        || dictSize < 0
1025        || source.is_null()
1026        || dest.is_null()
1027        || (dictSize > 0 && dictStart.is_null())
1028    {
1029        return -1;
1030    }
1031
1032    // Mirrors upstream `LZ4_decompress_safe_partial_usingDict` (lz4.c:2730-2743).
1033    if dictSize == 0 {
1034        return LZ4_decompress_safe_partial(
1035            source,
1036            dest,
1037            compressedSize,
1038            targetOutputSize,
1039            dstCapacity,
1040        );
1041    }
1042    let src = slice::from_raw_parts(source as *const u8, compressedSize as usize);
1043    let dst = slice::from_raw_parts_mut(dest as *mut u8, dstCapacity as usize);
1044    let dict = slice::from_raw_parts(dictStart as *const u8, dictSize as usize);
1045
1046    let dict_end = (dictStart as *const u8).add(dictSize as usize);
1047    if dict_end == dest as *const u8 {
1048        // Contiguous prefix — see `LZ4_decompress_safe_usingDict` for the
1049        // rationale on why both upstream prefix specialisations collapse to
1050        // the same Rust call.
1051        let _is_prefix_64k = dictSize as usize >= 64 * 1024 - 1;
1052        return decompress_block_partial_with_dict(
1053            src,
1054            dst,
1055            cmp::min(targetOutputSize as usize, dst.len()),
1056            dict,
1057        )
1058        .map_or(-1, |n| n as c_int);
1059    }
1060
1061    // forceExtDict path.
1062    decompress_block_partial_with_dict(
1063        src,
1064        dst,
1065        cmp::min(targetOutputSize as usize, dst.len()),
1066        dict,
1067    )
1068    .map_or(-1, |n| n as c_int)
1069}
1070
1071/// `LZ4_decoderRingBufferSize()` : v1.8.2+
1072/// Note : in a ring buffer scenario (optional), blocks are presumed decompressed next to each
1073/// other up to the moment there is not enough remaining space for next block
1074/// (`remainingSize < maxBlockSize`), at which stage it resumes from beginning of ring buffer.
1075/// When setting such a ring buffer for streaming decompression, provides the minimum size of
1076/// this ring buffer to be compatible with any source respecting `maxBlockSize` condition.
1077///
1078/// - return : minimum ring buffer size, or 0 if there is an error (invalid `maxBlockSize`).
1079#[no_mangle]
1080pub extern "C" fn LZ4_decoderRingBufferSize(maxBlockSize: c_int) -> c_int {
1081    if !(0..=LZ4_MAX_INPUT_SIZE).contains(&maxBlockSize) {
1082        return 0;
1083    }
1084    let max_block_size = cmp::max(maxBlockSize, 16);
1085    max_block_size.saturating_add((LZ4_DISTANCE_MAX + 1 + 14) as c_int)
1086}
1087
1088/// Deprecated since v1.9.0; use `LZ4_decompress_safe_partial()` instead.
1089///
1090/// `LZ4_decompress_fast()` decompresses a block knowing only the uncompressed size. It is
1091/// no longer faster than `LZ4_decompress_safe()` and is not protected against malformed or
1092/// malicious inputs.
1093///
1094/// - `originalSize` : the uncompressed size to regenerate. `dst` must be already allocated,
1095///   its size must be `>= originalSize` bytes.
1096/// - return : number of bytes read from source buffer (== compressed size). The function
1097///   expects to finish at block's end exactly. If the source stream is detected
1098///   malformed, the function stops decoding and returns a negative result.
1099///
1100/// Note : since `LZ4_decompress_fast*()` doesn't know its `src` size, it may read an unknown
1101/// amount of input, past input buffer bounds. Match offsets are not validated, so match reads
1102/// from `src` may underflow too. Use these functions in trusted environments with trusted
1103/// data **only**.
1104#[no_mangle]
1105pub unsafe extern "C" fn LZ4_decompress_fast(
1106    source: *const c_char,
1107    dest: *mut c_char,
1108    originalSize: c_int,
1109) -> c_int {
1110    if originalSize < 0 || source.is_null() || dest.is_null() {
1111        return -1;
1112    }
1113    let dst = slice::from_raw_parts_mut(dest as *mut u8, originalSize as usize);
1114    decompress_block_exact_ptr(source as *const u8, dst, &[]).map_or(-1, |n| n as c_int)
1115}
1116
1117/// Deprecated; use `LZ4_decompress_safe_partial_usingDict()` instead.
1118/// Variant of `LZ4_decompress_fast()` accepting an external dictionary. Same security
1119/// caveats apply: only use with trusted data.
1120#[no_mangle]
1121pub unsafe extern "C" fn LZ4_decompress_fast_usingDict(
1122    source: *const c_char,
1123    dest: *mut c_char,
1124    originalSize: c_int,
1125    dictStart: *const c_char,
1126    dictSize: c_int,
1127) -> c_int {
1128    if originalSize < 0
1129        || dictSize < 0
1130        || source.is_null()
1131        || dest.is_null()
1132        || (dictSize > 0 && dictStart.is_null())
1133    {
1134        return -1;
1135    }
1136    let dst = slice::from_raw_parts_mut(dest as *mut u8, originalSize as usize);
1137    let dict = if dictSize > 0 {
1138        slice::from_raw_parts(dictStart as *const u8, dictSize as usize)
1139    } else {
1140        &[]
1141    };
1142    decompress_block_exact_ptr(source as *const u8, dst, dict).map_or(-1, |n| n as c_int)
1143}
1144
1145/// Obsolete decompression function (since v1.8.0). Use `LZ4_decompress_fast()` instead.
1146#[no_mangle]
1147pub unsafe extern "C" fn LZ4_uncompress(
1148    source: *const c_char,
1149    dest: *mut c_char,
1150    outputSize: c_int,
1151) -> c_int {
1152    LZ4_decompress_fast(source, dest, outputSize)
1153}
1154
1155/// Obsolete decompression function (since v1.8.0). Use `LZ4_decompress_safe()` instead.
1156#[no_mangle]
1157pub unsafe extern "C" fn LZ4_uncompress_unknownOutputSize(
1158    source: *const c_char,
1159    dest: *mut c_char,
1160    isize: c_int,
1161    maxOutputSize: c_int,
1162) -> c_int {
1163    LZ4_decompress_safe(source, dest, isize, maxOutputSize)
1164}
1165
1166/// Obsolete streaming decoding function (since v1.7.0). Use `LZ4_decompress_safe_usingDict()`
1167/// instead.
1168#[no_mangle]
1169pub unsafe extern "C" fn LZ4_decompress_safe_withPrefix64k(
1170    source: *const c_char,
1171    dest: *mut c_char,
1172    compressedSize: c_int,
1173    maxDstSize: c_int,
1174) -> c_int {
1175    LZ4_decompress_safe_usingDict(
1176        source,
1177        dest,
1178        compressedSize,
1179        maxDstSize,
1180        if maxDstSize > 0 {
1181            dest.sub(cmp::min(maxDstSize as usize, LZ4_DISTANCE_MAX))
1182        } else {
1183            dest
1184        },
1185        cmp::min(maxDstSize.max(0) as usize, LZ4_DISTANCE_MAX) as c_int,
1186    )
1187}
1188
1189/// Obsolete streaming decoding function (since v1.7.0). Use `LZ4_decompress_fast_usingDict()`
1190/// instead.
1191#[no_mangle]
1192pub unsafe extern "C" fn LZ4_decompress_fast_withPrefix64k(
1193    source: *const c_char,
1194    dest: *mut c_char,
1195    originalSize: c_int,
1196) -> c_int {
1197    LZ4_decompress_fast_usingDict(
1198        source,
1199        dest,
1200        originalSize,
1201        if originalSize > 0 {
1202            dest.sub(cmp::min(originalSize as usize, LZ4_DISTANCE_MAX))
1203        } else {
1204            dest
1205        },
1206        cmp::min(originalSize.max(0) as usize, LZ4_DISTANCE_MAX) as c_int,
1207    )
1208}
1209
1210/// Deprecated; consider migrating towards `LZ4_decompress_safe_continue()` instead.
1211/// (Note that the contract differs: that variant requires the block's compressed size,
1212/// instead of decompressed size.)
1213///
1214/// Streaming variant of `LZ4_decompress_fast()` that uses the previously decoded data as a
1215/// dictionary. Same security caveats apply as `LZ4_decompress_fast()`: use only with trusted
1216/// data.
1217#[no_mangle]
1218pub unsafe extern "C" fn LZ4_decompress_fast_continue(
1219    stream: *mut LZ4StreamDecode,
1220    source: *const c_char,
1221    dest: *mut c_char,
1222    originalSize: c_int,
1223) -> c_int {
1224    if stream.is_null() || originalSize < 0 || source.is_null() || dest.is_null() {
1225        return -1;
1226    }
1227    let ctx = &mut *(stream as *mut DecodeStreamCtx);
1228    let dst = slice::from_raw_parts_mut(dest as *mut u8, originalSize as usize);
1229    match decompress_block_exact_ptr(source as *const u8, dst, &ctx.dictionary) {
1230        Some(n) => {
1231            append_hc_dictionary(&mut ctx.dictionary, dst);
1232            n as c_int
1233        }
1234        None => -1,
1235    }
1236}
1237
1238/// Tells when an `LZ4F_*` function result is an error code.
1239#[no_mangle]
1240pub extern "C" fn LZ4F_isError(code: size_t) -> c_uint {
1241    (code > lz4f_error(LZ4F_ERROR_MAX_CODE)) as c_uint
1242}
1243
1244/// Returns the error code string corresponding to `code`; for debugging.
1245#[no_mangle]
1246pub extern "C" fn LZ4F_getErrorName(code: size_t) -> *const c_char {
1247    match code {
1248        ERROR_MAX_BLOCK_SIZE_INVALID => ERROR_MAX_BLOCK_SIZE_NAME.as_ptr() as *const c_char,
1249        ERROR_BLOCK_MODE_INVALID => ERROR_BLOCK_MODE_NAME.as_ptr() as *const c_char,
1250        ERROR_PARAMETER_INVALID => ERROR_PARAMETER_INVALID_NAME.as_ptr() as *const c_char,
1251        ERROR_COMPRESSION_LEVEL_INVALID => ERROR_COMPRESSION_LEVEL_NAME.as_ptr() as *const c_char,
1252        ERROR_HEADER_VERSION_WRONG => ERROR_HEADER_VERSION_NAME.as_ptr() as *const c_char,
1253        ERROR_BLOCK_CHECKSUM_INVALID => ERROR_BLOCK_CHECKSUM_NAME.as_ptr() as *const c_char,
1254        ERROR_RESERVED_FLAG_SET => ERROR_RESERVED_FLAG_NAME.as_ptr() as *const c_char,
1255        ERROR_ALLOCATION_FAILED => ERROR_ALLOCATION_FAILED_NAME.as_ptr() as *const c_char,
1256        ERROR_SRC_SIZE_TOO_LARGE => ERROR_SRC_SIZE_TOO_LARGE_NAME.as_ptr() as *const c_char,
1257        ERROR_FRAME_SIZE_WRONG => ERROR_FRAME_SIZE_NAME.as_ptr() as *const c_char,
1258        ERROR_FRAME_TYPE_UNKNOWN => ERROR_FRAME_TYPE_NAME.as_ptr() as *const c_char,
1259        ERROR_SRC_PTR_WRONG => ERROR_SRC_PTR_NAME.as_ptr() as *const c_char,
1260        ERROR_DECOMPRESSION_FAILED => ERROR_DECOMPRESSION_FAILED_NAME.as_ptr() as *const c_char,
1261        ERROR_HEADER_CHECKSUM_INVALID => ERROR_HEADER_CHECKSUM_NAME.as_ptr() as *const c_char,
1262        ERROR_DST_TOO_SMALL => ERROR_DST_NAME.as_ptr() as *const c_char,
1263        ERROR_BAD_HEADER => ERROR_BAD_HEADER_NAME.as_ptr() as *const c_char,
1264        ERROR_CHECKSUM_INVALID => ERROR_CHECKSUM_NAME.as_ptr() as *const c_char,
1265        ERROR_FRAME_DECODING_ALREADY_STARTED => {
1266            ERROR_FRAME_DECODING_ALREADY_STARTED_NAME.as_ptr() as *const c_char
1267        }
1268        ERROR_COMPRESSION_STATE_UNINITIALIZED => {
1269            ERROR_COMPRESSION_STATE_UNINITIALIZED_NAME.as_ptr() as *const c_char
1270        }
1271        ERROR_PARAMETER_NULL => ERROR_PARAMETER_NULL_NAME.as_ptr() as *const c_char,
1272        ERROR_IO_WRITE => ERROR_IO_WRITE_NAME.as_ptr() as *const c_char,
1273        ERROR_IO_READ => ERROR_IO_READ_NAME.as_ptr() as *const c_char,
1274        ERROR_GENERIC => ERROR_GENERIC_NAME.as_ptr() as *const c_char,
1275        _ => ERROR_UNSPECIFIED_NAME.as_ptr() as *const c_char,
1276    }
1277}
1278
1279/// Returns the `LZ4F_errorCodes` enum value corresponding to a function result.
1280/// Returns 0 (`LZ4F_OK_NoError`) when the result is not an error.
1281#[no_mangle]
1282pub extern "C" fn LZ4F_getErrorCode(code: size_t) -> c_uint {
1283    if LZ4F_isError(code) == 0 {
1284        0
1285    } else {
1286        (usize::MAX - code + 1) as c_uint
1287    }
1288}
1289
1290/// Returns the value of `LZ4F_VERSION` the library was compiled with.
1291#[no_mangle]
1292pub extern "C" fn LZ4F_getVersion() -> c_uint {
1293    LZ4F_VERSION
1294}
1295
1296/// Returns the maximum allowed compression level (currently 12). Available since v1.8.0.
1297#[no_mangle]
1298pub extern "C" fn LZ4F_compressionLevel_max() -> c_int {
1299    LZ4HC_CLEVEL_MAX
1300}
1301
1302/// `LZ4F_getBlockSize()` :
1303/// - return : in scalar format (`size_t`), the maximum block size associated with
1304///   `blockSizeID`, or an error code (can be tested using `LZ4F_isError()`) if
1305///   `blockSizeID` is invalid.
1306#[no_mangle]
1307pub extern "C" fn LZ4F_getBlockSize(blockSizeID: c_uint) -> size_t {
1308    let blockSizeID = if blockSizeID == 0 { 4 } else { blockSizeID };
1309    if !(4..=7).contains(&blockSizeID) {
1310        return ERROR_MAX_BLOCK_SIZE_INVALID;
1311    }
1312    block_max_size(blockSizeID as u8)
1313}
1314
1315/// `LZ4F_createCompressionContext()` :
1316/// The first thing to do is to create a compressionContext object, which will keep track of
1317/// operation state during streaming compression. `version` provided MUST be `LZ4F_VERSION`.
1318/// It is intended to track potential version mismatch, notably when using DLL. The function
1319/// provides a pointer to a fully allocated `LZ4F_cctx` object. `cctxPtr` MUST be `!= NULL`.
1320/// If return `!= zero`, context creation failed. A created compression context can be employed
1321/// multiple times for consecutive streaming operations. Once all streaming compression jobs
1322/// are completed, the state object can be released using `LZ4F_freeCompressionContext()`.
1323#[no_mangle]
1324pub unsafe extern "C" fn LZ4F_createCompressionContext(
1325    ctx: &mut LZ4FCompressionContext,
1326    version: c_uint,
1327) -> LZ4FErrorCode {
1328    if version != LZ4F_VERSION {
1329        return ERROR_PARAMETER_INVALID;
1330    }
1331    let inner = Box::new(CompressionCtx {
1332        prefs: FramePrefs::default(),
1333        content_hasher: XxHash32::new(0),
1334        dictionary: Vec::new(),
1335        pending: Vec::new(),
1336        total_input: 0,
1337        external_dictionary: false,
1338        started: false,
1339    });
1340    ctx.0 = Box::into_raw(inner) as *mut c_void;
1341    0
1342}
1343
1344/// Releases an `LZ4F_cctx` previously created by `LZ4F_createCompressionContext()`.
1345/// Always successful: the return value can be ignored. Works fine with `NULL` input pointers
1346/// (does nothing).
1347#[no_mangle]
1348pub unsafe extern "C" fn LZ4F_freeCompressionContext(ctx: LZ4FCompressionContext) -> LZ4FErrorCode {
1349    if !ctx.0.is_null() {
1350        drop(Box::from_raw(ctx.0 as *mut CompressionCtx));
1351    }
1352    0
1353}
1354
1355/// Context size inspection (v1.10.1+). Returns the total memory footprint of the provided
1356/// compression context, or 0 if `ctx` is null.
1357#[no_mangle]
1358pub unsafe extern "C" fn LZ4F_cctx_size(ctx: LZ4FCompressionContext) -> size_t {
1359    if ctx.0.is_null() {
1360        0
1361    } else {
1362        std::mem::size_of::<CompressionCtx>()
1363    }
1364}
1365
1366/// `LZ4F_compressBegin()` :
1367/// Will write the frame header into `dstBuffer`. `dstCapacity` must be
1368/// `>= LZ4F_HEADER_SIZE_MAX` bytes. `prefsPtr` is optional: NULL can be provided to set all
1369/// preferences to default.
1370///
1371/// - return : number of bytes written into `dstBuffer` for the header, or an error code
1372///   (which can be tested using `LZ4F_isError()`).
1373#[no_mangle]
1374pub unsafe extern "C" fn LZ4F_compressBegin(
1375    ctx: LZ4FCompressionContext,
1376    dstBuffer: *mut u8,
1377    dstMaxSize: size_t,
1378    preferencesPtr: *const LZ4FPreferences,
1379) -> LZ4FErrorCode {
1380    if ctx.0.is_null() || dstBuffer.is_null() {
1381        return ERROR_PARAMETER_NULL;
1382    }
1383    let inner = &mut *(ctx.0 as *mut CompressionCtx);
1384    inner.prefs = preferences_from_ptr(preferencesPtr);
1385    inner.content_hasher = XxHash32::new(0);
1386    inner.dictionary.clear();
1387    inner.pending.clear();
1388    inner.total_input = 0;
1389    inner.external_dictionary = false;
1390    inner.started = true;
1391    let header = frame_header(inner.prefs);
1392    if dstMaxSize < header.len() {
1393        return ERROR_DST_TOO_SMALL;
1394    }
1395    ptr::copy_nonoverlapping(header.as_ptr(), dstBuffer, header.len());
1396    header.len()
1397}
1398
1399/// `LZ4F_compressBegin_usingDict()` : stable since v1.10
1400/// Initialises dictionary compression streaming, and writes the frame header into `dstBuffer`.
1401/// `dstCapacity` must be `>= LZ4F_HEADER_SIZE_MAX` bytes. `prefsPtr` is optional: one may
1402/// provide NULL as argument, however it's the only way to provide `dictID` in the frame
1403/// header. `dictBuffer` must outlive the compression session.
1404///
1405/// - return : number of bytes written into `dstBuffer` for the header, or an error code
1406///   (which can be tested using `LZ4F_isError()`).
1407///
1408/// NOTE: The LZ4Frame spec allows each independent block to be compressed with the dictionary,
1409/// but this entry supports a more limited scenario, where only the first block uses the
1410/// dictionary. For larger inputs, consider `LZ4F_compressFrame_usingCDict()`.
1411#[no_mangle]
1412pub unsafe extern "C" fn LZ4F_compressBegin_usingDict(
1413    ctx: LZ4FCompressionContext,
1414    dstBuffer: *mut c_void,
1415    dstCapacity: size_t,
1416    dictBuffer: *const c_void,
1417    dictSize: size_t,
1418    preferencesPtr: *const LZ4FPreferences,
1419) -> LZ4FErrorCode {
1420    if dictSize > 0 && dictBuffer.is_null() {
1421        return ERROR_SRC_PTR_WRONG;
1422    }
1423    let written = LZ4F_compressBegin(ctx, dstBuffer as *mut u8, dstCapacity, preferencesPtr);
1424    if LZ4F_isError(written) != 0 {
1425        return written;
1426    }
1427    if dictSize > 0 {
1428        let inner = &mut *(ctx.0 as *mut CompressionCtx);
1429        let dict = slice::from_raw_parts(dictBuffer as *const u8, dictSize);
1430        let keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
1431        inner.dictionary.clear();
1432        inner
1433            .dictionary
1434            .extend_from_slice(&dict[dict.len() - keep..]);
1435        inner.external_dictionary = true;
1436    }
1437    written
1438}
1439
1440/// `LZ4F_createCDict()` : stable since v1.10
1441/// When compressing multiple messages / blocks using the same dictionary, it's recommended
1442/// to initialise it just once. `LZ4F_createCDict()` will create a digested dictionary,
1443/// ready to start future compression operations without startup delay. An `LZ4F_CDict` can
1444/// be created once and shared by multiple threads concurrently, since its usage is read-only.
1445/// `dictBuffer` can be released after `LZ4F_CDict` creation, since its content is copied
1446/// within the CDict.
1447#[no_mangle]
1448pub unsafe extern "C" fn LZ4F_createCDict(
1449    dictBuffer: *const c_void,
1450    dictSize: size_t,
1451) -> *mut LZ4FCDict {
1452    if dictSize > 0 && dictBuffer.is_null() {
1453        return ptr::null_mut();
1454    }
1455    let mut ctx = CDictCtx::default();
1456    if dictSize > 0 {
1457        let dict = slice::from_raw_parts(dictBuffer as *const u8, dictSize);
1458        let keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
1459        ctx.dictionary.extend_from_slice(&dict[dict.len() - keep..]);
1460    }
1461    Box::into_raw(Box::new(ctx)) as *mut LZ4FCDict
1462}
1463
1464/// Releases an `LZ4F_CDict` created by `LZ4F_createCDict()`. Does nothing if `cdict` is null.
1465#[no_mangle]
1466pub unsafe extern "C" fn LZ4F_freeCDict(cdict: *mut LZ4FCDict) {
1467    if !cdict.is_null() {
1468        drop(Box::from_raw(cdict as *mut CDictCtx));
1469    }
1470}
1471
1472/// `LZ4F_compressBegin_usingCDict()` : stable since v1.10
1473/// Initialises streaming dictionary compression, and writes the frame header into `dstBuffer`.
1474/// `dstCapacity` must be `>= LZ4F_HEADER_SIZE_MAX` bytes. `prefsPtr` is optional: one may
1475/// provide NULL as argument, but note that it's the only way to insert a `dictID` in the
1476/// frame header. `cdict` must outlive the compression session.
1477///
1478/// - return : number of bytes written into `dstBuffer` for the header, or an error code,
1479///   which can be tested using `LZ4F_isError()`.
1480#[no_mangle]
1481pub unsafe extern "C" fn LZ4F_compressBegin_usingCDict(
1482    ctx: LZ4FCompressionContext,
1483    dstBuffer: *mut c_void,
1484    dstCapacity: size_t,
1485    cdict: *const LZ4FCDict,
1486    preferencesPtr: *const LZ4FPreferences,
1487) -> size_t {
1488    if cdict.is_null() {
1489        return LZ4F_compressBegin(ctx, dstBuffer as *mut u8, dstCapacity, preferencesPtr);
1490    }
1491    let dict = &*(cdict as *const CDictCtx);
1492    LZ4F_compressBegin_usingDict(
1493        ctx,
1494        dstBuffer,
1495        dstCapacity,
1496        dict.dictionary.as_ptr() as *const c_void,
1497        dict.dictionary.len(),
1498        preferencesPtr,
1499    )
1500}
1501
1502/// `LZ4F_compressBound()` :
1503/// Provides minimum `dstCapacity` required to guarantee success of `LZ4F_compressUpdate()`,
1504/// given a `srcSize` and preferences, for a worst case scenario. When `srcSize == 0`,
1505/// `LZ4F_compressBound()` provides an upper bound for `LZ4F_flush()` and `LZ4F_compressEnd()`
1506/// instead. Note that the result is only valid for a single invocation of
1507/// `LZ4F_compressUpdate()`. When invoking `LZ4F_compressUpdate()` multiple times, if the
1508/// output buffer is gradually filled up instead of emptied and re-used from its start, one
1509/// must check if there is enough remaining capacity before each invocation, using
1510/// `LZ4F_compressBound()`.
1511///
1512/// `prefsPtr` is optional: when NULL is provided, preferences will be set to cover the
1513/// worst case scenario.
1514///
1515/// tech details: if automatic flushing is not enabled, the return value includes the
1516/// possibility that internal buffer might already be filled by up to `(blockSize-1)` bytes.
1517/// It also includes frame footer (ending + checksum), since it might be generated by
1518/// `LZ4F_compressEnd()`. It does not include the frame header, as it was already generated
1519/// by `LZ4F_compressBegin()`.
1520#[no_mangle]
1521pub unsafe extern "C" fn LZ4F_compressBound(
1522    srcSize: size_t,
1523    preferencesPtr: *const LZ4FPreferences,
1524) -> size_t {
1525    let prefs = preferences_from_ptr(preferencesPtr);
1526    let checksums = if prefs.block_checksum { 4 } else { 0 };
1527    let block_max = block_max_size(prefs.block_size_id);
1528    let buffered = if prefs.auto_flush { 0 } else { block_max };
1529    let total = srcSize.saturating_add(buffered);
1530    let blocks = if total == 0 {
1531        1
1532    } else {
1533        total.div_ceil(block_max)
1534    };
1535    total + blocks * (4 + checksums) + 16
1536}
1537
1538/// `LZ4F_compressFrameBound()` :
1539/// Returns the maximum possible compressed size with `LZ4F_compressFrame()` given `srcSize`
1540/// and preferences. `preferencesPtr` is optional: it can be replaced by NULL, in which case
1541/// the function will assume default preferences.
1542///
1543/// Note : this result is only usable with `LZ4F_compressFrame()`. It may also be relevant to
1544/// `LZ4F_compressUpdate()` _only if_ no `flush()` operation is ever performed.
1545#[no_mangle]
1546pub unsafe extern "C" fn LZ4F_compressFrameBound(
1547    srcSize: size_t,
1548    preferencesPtr: *const LZ4FPreferences,
1549) -> size_t {
1550    LZ4F_compressBound(srcSize, preferencesPtr) + 19 + 4
1551}
1552
1553/// `LZ4F_compressFrame()` :
1554/// Compress `srcBuffer` content into an LZ4-compressed frame. It's a one shot operation,
1555/// all input content is consumed, and all output is generated.
1556///
1557/// Note : it's a stateless operation (no `LZ4F_cctx` state needed).
1558///
1559/// - `dstCapacity` MUST be `>= LZ4F_compressFrameBound(srcSize, preferencesPtr)`.
1560/// - `preferencesPtr` is optional: one can provide NULL, in which case all preferences are
1561///   set to default.
1562/// - return : number of bytes written into `dstBuffer`, or an error code if it fails (can
1563///   be tested using `LZ4F_isError()`).
1564#[no_mangle]
1565pub unsafe extern "C" fn LZ4F_compressFrame(
1566    dstBuffer: *mut c_void,
1567    dstCapacity: size_t,
1568    srcBuffer: *const c_void,
1569    srcSize: size_t,
1570    preferencesPtr: *const LZ4FPreferences,
1571) -> size_t {
1572    if dstBuffer.is_null() || (srcSize > 0 && srcBuffer.is_null()) {
1573        return ERROR_PARAMETER_NULL;
1574    }
1575    let mut preferences;
1576    let begin_preferences = if preferencesPtr.is_null() {
1577        ptr::null()
1578    } else {
1579        preferences = (*preferencesPtr).clone();
1580        if preferences.frame_info.content_size != 0 {
1581            preferences.frame_info.content_size = srcSize as u64;
1582        }
1583        &preferences
1584    };
1585    let mut ctx = LZ4FCompressionContext(ptr::null_mut());
1586    let code = LZ4F_createCompressionContext(&mut ctx, LZ4F_VERSION);
1587    if LZ4F_isError(code) != 0 {
1588        return code;
1589    }
1590
1591    let dst = dstBuffer as *mut u8;
1592    let mut pos = LZ4F_compressBegin(ctx, dst, dstCapacity, begin_preferences);
1593    if LZ4F_isError(pos) != 0 {
1594        LZ4F_freeCompressionContext(ctx);
1595        return pos;
1596    }
1597    let update = LZ4F_compressUpdate(
1598        ctx,
1599        dst.add(pos),
1600        dstCapacity.saturating_sub(pos),
1601        srcBuffer as *const u8,
1602        srcSize,
1603        ptr::null(),
1604    );
1605    if LZ4F_isError(update) != 0 {
1606        LZ4F_freeCompressionContext(ctx);
1607        return update;
1608    }
1609    pos += update;
1610    let end = LZ4F_compressEnd(
1611        ctx,
1612        dst.add(pos),
1613        dstCapacity.saturating_sub(pos),
1614        ptr::null(),
1615    );
1616    LZ4F_freeCompressionContext(ctx);
1617    if LZ4F_isError(end) != 0 {
1618        return end;
1619    }
1620    pos + end
1621}
1622
1623/// `LZ4F_compressFrame_usingCDict()` : stable since v1.10
1624/// Compress an entire `srcBuffer` into a valid LZ4 frame using a digested Dictionary.
1625/// `cctx` must point to a context created by `LZ4F_createCompressionContext()`. If
1626/// `cdict == NULL`, compress without a dictionary. `dstBuffer` MUST be
1627/// `>= LZ4F_compressFrameBound(srcSize, preferencesPtr)`. If this condition is not
1628/// respected, function will fail (return an errorCode). The `LZ4F_preferences_t` structure
1629/// is optional: one may provide NULL as argument, but it's not recommended, as it's the
1630/// only way to provide `dictID` in the frame header.
1631///
1632/// - return : number of bytes written into `dstBuffer`, or an error code if it fails (can
1633///   be tested using `LZ4F_isError()`).
1634///
1635/// Note: for larger inputs generating multiple independent blocks, this entry point uses
1636/// the dictionary for each block.
1637#[no_mangle]
1638pub unsafe extern "C" fn LZ4F_compressFrame_usingCDict(
1639    ctx: LZ4FCompressionContext,
1640    dstBuffer: *mut c_void,
1641    dstCapacity: size_t,
1642    srcBuffer: *const c_void,
1643    srcSize: size_t,
1644    cdict: *const LZ4FCDict,
1645    preferencesPtr: *const LZ4FPreferences,
1646) -> size_t {
1647    if ctx.0.is_null() || dstBuffer.is_null() || (srcSize > 0 && srcBuffer.is_null()) {
1648        return ERROR_PARAMETER_NULL;
1649    }
1650    let dst = dstBuffer as *mut u8;
1651    let mut pos = LZ4F_compressBegin_usingCDict(ctx, dstBuffer, dstCapacity, cdict, preferencesPtr);
1652    if LZ4F_isError(pos) != 0 {
1653        return pos;
1654    }
1655    let update = LZ4F_compressUpdate(
1656        ctx,
1657        dst.add(pos),
1658        dstCapacity.saturating_sub(pos),
1659        srcBuffer as *const u8,
1660        srcSize,
1661        ptr::null(),
1662    );
1663    if LZ4F_isError(update) != 0 {
1664        return update;
1665    }
1666    pos += update;
1667    let end = LZ4F_compressEnd(
1668        ctx,
1669        dst.add(pos),
1670        dstCapacity.saturating_sub(pos),
1671        ptr::null(),
1672    );
1673    if LZ4F_isError(end) != 0 {
1674        return end;
1675    }
1676    pos + end
1677}
1678
1679/// `LZ4F_compressUpdate()` :
1680/// Can be called repetitively to compress as much data as necessary.
1681///
1682/// Important rule: `dstCapacity` MUST be large enough to ensure operation success even in
1683/// worst case situations. This value is provided by `LZ4F_compressBound()`. If this
1684/// condition is not respected, `LZ4F_compressUpdate()` will fail (result is an errorCode).
1685/// After an error, the state is left in a UB state, and must be re-initialised or freed.
1686///
1687/// If previously an uncompressed block was written, buffered data is flushed before
1688/// appending compressed data is continued.
1689///
1690/// `cOptPtr` is optional: NULL can be provided, in which case all options are set to default.
1691///
1692/// - return : number of bytes written into `dstBuffer` (it can be zero, meaning input data
1693///   was just buffered), or an error code if it fails (which can be tested using
1694///   `LZ4F_isError()`).
1695#[no_mangle]
1696pub unsafe extern "C" fn LZ4F_compressUpdate(
1697    ctx: LZ4FCompressionContext,
1698    dstBuffer: *mut u8,
1699    dstCapacity: size_t,
1700    srcBuffer: *const u8,
1701    srcSize: size_t,
1702    _cOptPtr: *const LZ4FCompressOptions,
1703) -> size_t {
1704    if ctx.0.is_null() || dstBuffer.is_null() || (srcSize > 0 && srcBuffer.is_null()) {
1705        return ERROR_PARAMETER_NULL;
1706    }
1707    let inner = &mut *(ctx.0 as *mut CompressionCtx);
1708    if !inner.started {
1709        return ERROR_COMPRESSION_STATE_UNINITIALIZED;
1710    }
1711    let src = if srcSize > 0 {
1712        slice::from_raw_parts(srcBuffer, srcSize)
1713    } else {
1714        &[]
1715    };
1716    let block_max = block_max_size(inner.prefs.block_size_id);
1717    let dst = slice::from_raw_parts_mut(dstBuffer, dstCapacity);
1718    if inner.prefs.auto_flush {
1719        return compress_frame_update_blocks(inner, src, dst, true);
1720    }
1721
1722    let mut written = 0usize;
1723    if !inner.pending.is_empty() {
1724        let fill = cmp::min(block_max - inner.pending.len(), src.len());
1725        inner.pending.extend_from_slice(&src[..fill]);
1726        if inner.pending.len() < block_max {
1727            return 0;
1728        }
1729
1730        let pending = std::mem::take(&mut inner.pending);
1731        let n = compress_frame_update_block(inner, &pending, dst);
1732        inner.pending = pending;
1733        inner.pending.clear();
1734        if LZ4F_isError(n) != 0 {
1735            return n;
1736        }
1737        written += n;
1738        let rest = &src[fill..];
1739        let full_len = rest.len() / block_max * block_max;
1740        if full_len > 0 {
1741            let Some(remaining_dst) = dst.get_mut(written..) else {
1742                return ERROR_DST_TOO_SMALL;
1743            };
1744            let n = compress_frame_update_blocks(inner, &rest[..full_len], remaining_dst, false);
1745            if LZ4F_isError(n) != 0 {
1746                return n;
1747            }
1748            written += n;
1749        }
1750        if full_len < rest.len() {
1751            inner.pending.extend_from_slice(&rest[full_len..]);
1752        }
1753        return written;
1754    }
1755
1756    let full_len = src.len() / block_max * block_max;
1757    if full_len > 0 {
1758        let n = compress_frame_update_blocks(inner, &src[..full_len], dst, false);
1759        if LZ4F_isError(n) != 0 {
1760            return n;
1761        }
1762        written += n;
1763    }
1764    if full_len < src.len() {
1765        inner.pending.extend_from_slice(&src[full_len..]);
1766    }
1767    written
1768}
1769
1770fn compress_frame_update_block(inner: &mut CompressionCtx, src: &[u8], dst: &mut [u8]) -> size_t {
1771    match (
1772        inner.prefs.block_checksum,
1773        inner.prefs.content_checksum,
1774        inner.prefs.block_independent,
1775    ) {
1776        (false, false, false) => {
1777            compress_frame_update_block_flags::<false, false, false>(inner, src, dst)
1778        }
1779        (false, false, true) => {
1780            compress_frame_update_block_flags::<false, false, true>(inner, src, dst)
1781        }
1782        (false, true, false) => {
1783            compress_frame_update_block_flags::<false, true, false>(inner, src, dst)
1784        }
1785        (false, true, true) => {
1786            compress_frame_update_block_flags::<false, true, true>(inner, src, dst)
1787        }
1788        (true, false, false) => {
1789            compress_frame_update_block_flags::<true, false, false>(inner, src, dst)
1790        }
1791        (true, false, true) => {
1792            compress_frame_update_block_flags::<true, false, true>(inner, src, dst)
1793        }
1794        (true, true, false) => {
1795            compress_frame_update_block_flags::<true, true, false>(inner, src, dst)
1796        }
1797        (true, true, true) => {
1798            compress_frame_update_block_flags::<true, true, true>(inner, src, dst)
1799        }
1800    }
1801}
1802
1803fn compress_frame_update_block_flags<
1804    const BLOCK_CHECKSUM: bool,
1805    const CONTENT_CHECKSUM: bool,
1806    const BLOCK_INDEPENDENT: bool,
1807>(
1808    inner: &mut CompressionCtx,
1809    src: &[u8],
1810    dst: &mut [u8],
1811) -> size_t {
1812    let checksum_len = if BLOCK_CHECKSUM { 4 } else { 0 };
1813    let raw_needed = 4 + src.len() + checksum_len;
1814    if dst.len() < raw_needed {
1815        return ERROR_DST_TOO_SMALL;
1816    }
1817
1818    let compressed_len = compress_frame_block(src, &mut dst[4..], &inner.prefs, &inner.dictionary);
1819    let (block_len, raw) = match compressed_len {
1820        Some(len) if len < src.len() => (len, false),
1821        _ => {
1822            dst[4..4 + src.len()].copy_from_slice(src);
1823            (src.len(), true)
1824        }
1825    };
1826
1827    let block_size = (block_len as u32) | if raw { 0x8000_0000 } else { 0 };
1828    dst[..4].copy_from_slice(&block_size.to_le_bytes());
1829    if CONTENT_CHECKSUM {
1830        inner.content_hasher.update(src);
1831    }
1832    inner.total_input = inner.total_input.saturating_add(src.len() as u64);
1833    if !BLOCK_INDEPENDENT {
1834        append_hc_dictionary(&mut inner.dictionary, src);
1835        inner.external_dictionary = false;
1836    } else if inner.external_dictionary {
1837        inner.dictionary.clear();
1838        inner.external_dictionary = false;
1839    }
1840    let needed = 4 + block_len + checksum_len;
1841    if BLOCK_CHECKSUM {
1842        let checksum = xxhash32(&dst[4..4 + block_len], 0);
1843        dst[4 + block_len..needed].copy_from_slice(&checksum.to_le_bytes());
1844    }
1845    needed
1846}
1847
1848fn compress_frame_update_blocks(
1849    inner: &mut CompressionCtx,
1850    src: &[u8],
1851    dst: &mut [u8],
1852    emit_partial: bool,
1853) -> size_t {
1854    match (
1855        inner.prefs.block_checksum,
1856        inner.prefs.content_checksum,
1857        inner.prefs.block_independent,
1858    ) {
1859        (false, false, false) => {
1860            compress_frame_update_blocks_flags::<false, false, false>(inner, src, dst, emit_partial)
1861        }
1862        (false, false, true) => {
1863            compress_frame_update_blocks_flags::<false, false, true>(inner, src, dst, emit_partial)
1864        }
1865        (false, true, false) => {
1866            compress_frame_update_blocks_flags::<false, true, false>(inner, src, dst, emit_partial)
1867        }
1868        (false, true, true) => {
1869            compress_frame_update_blocks_flags::<false, true, true>(inner, src, dst, emit_partial)
1870        }
1871        (true, false, false) => {
1872            compress_frame_update_blocks_flags::<true, false, false>(inner, src, dst, emit_partial)
1873        }
1874        (true, false, true) => {
1875            compress_frame_update_blocks_flags::<true, false, true>(inner, src, dst, emit_partial)
1876        }
1877        (true, true, false) => {
1878            compress_frame_update_blocks_flags::<true, true, false>(inner, src, dst, emit_partial)
1879        }
1880        (true, true, true) => {
1881            compress_frame_update_blocks_flags::<true, true, true>(inner, src, dst, emit_partial)
1882        }
1883    }
1884}
1885
1886fn compress_frame_update_blocks_flags<
1887    const BLOCK_CHECKSUM: bool,
1888    const CONTENT_CHECKSUM: bool,
1889    const BLOCK_INDEPENDENT: bool,
1890>(
1891    inner: &mut CompressionCtx,
1892    src: &[u8],
1893    dst: &mut [u8],
1894    emit_partial: bool,
1895) -> size_t {
1896    let block_max = block_max_size(inner.prefs.block_size_id);
1897    let mut written = 0usize;
1898    let mut chunks = src.chunks_exact(block_max);
1899    for chunk in &mut chunks {
1900        let Some(remaining_dst) = dst.get_mut(written..) else {
1901            return ERROR_DST_TOO_SMALL;
1902        };
1903        let n = compress_frame_update_block_flags::<
1904            BLOCK_CHECKSUM,
1905            CONTENT_CHECKSUM,
1906            BLOCK_INDEPENDENT,
1907        >(inner, chunk, remaining_dst);
1908        if LZ4F_isError(n) != 0 {
1909            return n;
1910        }
1911        written += n;
1912    }
1913    let remainder = chunks.remainder();
1914    if emit_partial && !remainder.is_empty() {
1915        let Some(remaining_dst) = dst.get_mut(written..) else {
1916            return ERROR_DST_TOO_SMALL;
1917        };
1918        let n = compress_frame_update_block_flags::<
1919            BLOCK_CHECKSUM,
1920            CONTENT_CHECKSUM,
1921            BLOCK_INDEPENDENT,
1922        >(inner, remainder, remaining_dst);
1923        if LZ4F_isError(n) != 0 {
1924            return n;
1925        }
1926        written += n;
1927    }
1928    written
1929}
1930
1931/// `LZ4F_uncompressedUpdate()` :
1932/// Can be called repetitively to add data stored as uncompressed blocks.
1933///
1934/// Important rule: `dstCapacity` MUST be large enough to store the entire source buffer as
1935/// no compression is done for this operation. If this condition is not respected,
1936/// `LZ4F_uncompressedUpdate()` will fail (result is an errorCode). After an error, the
1937/// state is left in a UB state, and must be re-initialised or freed.
1938///
1939/// If previously a compressed block was written, buffered data is flushed first, before
1940/// appending uncompressed data is continued. This operation is only supported when
1941/// `LZ4F_blockIndependent` is used.
1942///
1943/// `cOptPtr` is optional: NULL can be provided, in which case all options are set to default.
1944///
1945/// - return : number of bytes written into `dstBuffer` (it can be zero, meaning input data
1946///   was just buffered), or an error code if it fails (which can be tested using
1947///   `LZ4F_isError()`).
1948#[no_mangle]
1949pub unsafe extern "C" fn LZ4F_uncompressedUpdate(
1950    ctx: LZ4FCompressionContext,
1951    dstBuffer: *mut c_void,
1952    dstCapacity: size_t,
1953    srcBuffer: *const c_void,
1954    srcSize: size_t,
1955    _cOptPtr: *const LZ4FCompressOptions,
1956) -> size_t {
1957    if ctx.0.is_null() || dstBuffer.is_null() || (srcSize > 0 && srcBuffer.is_null()) {
1958        return ERROR_PARAMETER_NULL;
1959    }
1960    let inner = &mut *(ctx.0 as *mut CompressionCtx);
1961    if !inner.started {
1962        return ERROR_COMPRESSION_STATE_UNINITIALIZED;
1963    }
1964    let src = if srcSize > 0 {
1965        slice::from_raw_parts(srcBuffer as *const u8, srcSize)
1966    } else {
1967        &[]
1968    };
1969    let dst = slice::from_raw_parts_mut(dstBuffer as *mut u8, dstCapacity);
1970    compress_frame_raw_blocks(inner, src, dst)
1971}
1972
1973fn compress_frame_raw_block_flags<
1974    const BLOCK_CHECKSUM: bool,
1975    const CONTENT_CHECKSUM: bool,
1976    const BLOCK_INDEPENDENT: bool,
1977>(
1978    inner: &mut CompressionCtx,
1979    src: &[u8],
1980    dst: &mut [u8],
1981) -> size_t {
1982    let checksum_len = if BLOCK_CHECKSUM { 4 } else { 0 };
1983    let needed = 4 + src.len() + checksum_len;
1984    if dst.len() < needed {
1985        return ERROR_DST_TOO_SMALL;
1986    }
1987    let block_size = (src.len() as u32) | 0x8000_0000;
1988    dst[..4].copy_from_slice(&block_size.to_le_bytes());
1989    dst[4..4 + src.len()].copy_from_slice(src);
1990    if BLOCK_CHECKSUM {
1991        let checksum = xxhash32(&dst[4..4 + src.len()], 0);
1992        dst[4 + src.len()..needed].copy_from_slice(&checksum.to_le_bytes());
1993    }
1994    if CONTENT_CHECKSUM {
1995        inner.content_hasher.update(src);
1996    }
1997    inner.total_input = inner.total_input.saturating_add(src.len() as u64);
1998    if !BLOCK_INDEPENDENT {
1999        append_hc_dictionary(&mut inner.dictionary, src);
2000    } else if inner.external_dictionary {
2001        inner.dictionary.clear();
2002        inner.external_dictionary = false;
2003    }
2004    needed
2005}
2006
2007fn compress_frame_raw_blocks(inner: &mut CompressionCtx, src: &[u8], dst: &mut [u8]) -> size_t {
2008    match (
2009        inner.prefs.block_checksum,
2010        inner.prefs.content_checksum,
2011        inner.prefs.block_independent,
2012    ) {
2013        (false, false, false) => {
2014            compress_frame_raw_blocks_flags::<false, false, false>(inner, src, dst)
2015        }
2016        (false, false, true) => {
2017            compress_frame_raw_blocks_flags::<false, false, true>(inner, src, dst)
2018        }
2019        (false, true, false) => {
2020            compress_frame_raw_blocks_flags::<false, true, false>(inner, src, dst)
2021        }
2022        (false, true, true) => {
2023            compress_frame_raw_blocks_flags::<false, true, true>(inner, src, dst)
2024        }
2025        (true, false, false) => {
2026            compress_frame_raw_blocks_flags::<true, false, false>(inner, src, dst)
2027        }
2028        (true, false, true) => {
2029            compress_frame_raw_blocks_flags::<true, false, true>(inner, src, dst)
2030        }
2031        (true, true, false) => {
2032            compress_frame_raw_blocks_flags::<true, true, false>(inner, src, dst)
2033        }
2034        (true, true, true) => compress_frame_raw_blocks_flags::<true, true, true>(inner, src, dst),
2035    }
2036}
2037
2038fn compress_frame_raw_blocks_flags<
2039    const BLOCK_CHECKSUM: bool,
2040    const CONTENT_CHECKSUM: bool,
2041    const BLOCK_INDEPENDENT: bool,
2042>(
2043    inner: &mut CompressionCtx,
2044    src: &[u8],
2045    dst: &mut [u8],
2046) -> size_t {
2047    let block_max = block_max_size(inner.prefs.block_size_id);
2048    let mut written = 0usize;
2049    for chunk in src.chunks(block_max) {
2050        let Some(remaining_dst) = dst.get_mut(written..) else {
2051            return ERROR_DST_TOO_SMALL;
2052        };
2053        let n = compress_frame_raw_block_flags::<BLOCK_CHECKSUM, CONTENT_CHECKSUM, BLOCK_INDEPENDENT>(
2054            inner,
2055            chunk,
2056            remaining_dst,
2057        );
2058        if LZ4F_isError(n) != 0 {
2059            return n;
2060        }
2061        written += n;
2062    }
2063    written
2064}
2065
2066/// `LZ4F_flush()` :
2067/// When data must be generated and sent immediately, without waiting for a block to be
2068/// completely filled, it's possible to call `LZ4F_flush()`. It will immediately compress
2069/// any data buffered within `cctx`.
2070///
2071/// `dstCapacity` must be large enough to ensure the operation will be successful. `cOptPtr`
2072/// is optional: it's possible to provide NULL, all options will be set to default.
2073///
2074/// - return : nb of bytes written into `dstBuffer` (can be zero, when there is no data
2075///   stored within `cctx`), or an error code if it fails (which can be tested using
2076///   `LZ4F_isError()`).
2077///
2078/// Note : `LZ4F_flush()` is guaranteed to be successful when
2079/// `dstCapacity >= LZ4F_compressBound(0, prefsPtr)`.
2080#[no_mangle]
2081pub unsafe extern "C" fn LZ4F_flush(
2082    ctx: LZ4FCompressionContext,
2083    dstBuffer: *mut u8,
2084    dstCapacity: size_t,
2085    _cOptPtr: *const LZ4FCompressOptions,
2086) -> size_t {
2087    if ctx.0.is_null() || dstBuffer.is_null() {
2088        return ERROR_PARAMETER_NULL;
2089    }
2090    let inner = &mut *(ctx.0 as *mut CompressionCtx);
2091    if !inner.started {
2092        return ERROR_COMPRESSION_STATE_UNINITIALIZED;
2093    }
2094    if inner.pending.is_empty() {
2095        return 0;
2096    }
2097    let dst = slice::from_raw_parts_mut(dstBuffer, dstCapacity);
2098    let pending = std::mem::take(&mut inner.pending);
2099    let n = compress_frame_update_block(inner, &pending, dst);
2100    if LZ4F_isError(n) == 0 {
2101        inner.pending.clear();
2102    } else {
2103        inner.pending = pending;
2104    }
2105    n
2106}
2107
2108/// `LZ4F_compressEnd()` :
2109/// To properly finish an LZ4 frame, invoke `LZ4F_compressEnd()`. It will flush whatever
2110/// data remained within `cctx` (like `LZ4F_flush()`) and properly finalise the frame, with
2111/// an endMark and a checksum. `cOptPtr` is optional: NULL can be provided, in which case
2112/// all options will be set to default.
2113///
2114/// - return : nb of bytes written into `dstBuffer`, necessarily `>= 4` (endMark), or an
2115///   error code if it fails (which can be tested using `LZ4F_isError()`).
2116///
2117/// Note : `LZ4F_compressEnd()` is guaranteed to be successful when
2118/// `dstCapacity >= LZ4F_compressBound(0, prefsPtr)`. A successful call makes `cctx` available
2119/// again for another compression task.
2120#[no_mangle]
2121pub unsafe extern "C" fn LZ4F_compressEnd(
2122    ctx: LZ4FCompressionContext,
2123    dstBuffer: *mut u8,
2124    dstCapacity: size_t,
2125    _cOptPtr: *const LZ4FCompressOptions,
2126) -> size_t {
2127    if ctx.0.is_null() || dstBuffer.is_null() {
2128        return ERROR_PARAMETER_NULL;
2129    }
2130    let inner = &mut *(ctx.0 as *mut CompressionCtx);
2131    if !inner.started {
2132        return ERROR_COMPRESSION_STATE_UNINITIALIZED;
2133    }
2134    let footer_len = 4 + if inner.prefs.content_checksum { 4 } else { 0 };
2135    if dstCapacity < footer_len {
2136        return ERROR_DST_TOO_SMALL;
2137    }
2138    let dst = slice::from_raw_parts_mut(dstBuffer, dstCapacity);
2139    let mut pos = 0usize;
2140    if !inner.pending.is_empty() {
2141        let checksum_len = if inner.prefs.block_checksum { 4 } else { 0 };
2142        let max_pending_len = 4 + inner.pending.len() + checksum_len + footer_len;
2143        if dstCapacity < max_pending_len {
2144            return ERROR_DST_TOO_SMALL;
2145        }
2146        let pending = std::mem::take(&mut inner.pending);
2147        let n = compress_frame_update_block(inner, &pending, dst);
2148        if LZ4F_isError(n) != 0 {
2149            inner.pending = pending;
2150            return n;
2151        }
2152        pos += n;
2153    }
2154    if inner.prefs.content_size != 0 && inner.total_input != inner.prefs.content_size {
2155        return ERROR_FRAME_SIZE_WRONG;
2156    }
2157    if dstCapacity - pos < footer_len {
2158        return ERROR_DST_TOO_SMALL;
2159    }
2160    dst[pos..pos + 4].copy_from_slice(&0u32.to_le_bytes());
2161    if inner.prefs.content_checksum {
2162        dst[pos + 4..pos + 8].copy_from_slice(&inner.content_hasher.digest().to_le_bytes());
2163    }
2164    inner.started = false;
2165    pos + footer_len
2166}
2167
2168/// `LZ4F_createDecompressionContext()` :
2169/// Create an `LZ4F_dctx` object, to track all decompression operations. `version` provided
2170/// MUST be `LZ4F_VERSION`. `dctxPtr` MUST be valid. The function fills `dctxPtr` with the
2171/// value of a pointer to an allocated and initialised `LZ4F_dctx` object. The return is an
2172/// errorCode, which can be tested using `LZ4F_isError()`. dctx memory can be released using
2173/// `LZ4F_freeDecompressionContext()`; its result indicates current state of decompression
2174/// context when being released. That is, it should be `== 0` if decompression has been
2175/// completed fully and correctly.
2176#[no_mangle]
2177pub unsafe extern "C" fn LZ4F_createDecompressionContext(
2178    ctx: &mut LZ4FDecompressionContext,
2179    version: c_uint,
2180) -> LZ4FErrorCode {
2181    if version != LZ4F_VERSION {
2182        return ERROR_PARAMETER_INVALID;
2183    }
2184    ctx.0 = Box::into_raw(Box::<DecompressionCtx>::default()) as *mut c_void;
2185    0
2186}
2187
2188/// Releases an `LZ4F_dctx` previously created by `LZ4F_createDecompressionContext()`.
2189#[no_mangle]
2190pub unsafe extern "C" fn LZ4F_freeDecompressionContext(
2191    ctx: LZ4FDecompressionContext,
2192) -> LZ4FErrorCode {
2193    if !ctx.0.is_null() {
2194        let boxed = Box::from_raw(ctx.0 as *mut DecompressionCtx);
2195        let status = decompression_free_status(&boxed);
2196        drop(boxed);
2197        return status;
2198    }
2199    0
2200}
2201
2202/// Context size inspection (v1.10.1+). Returns the total memory footprint of the provided
2203/// decompression context, or 0 if `ctx` is null.
2204#[no_mangle]
2205pub unsafe extern "C" fn LZ4F_dctx_size(ctx: LZ4FDecompressionContext) -> size_t {
2206    if ctx.0.is_null() {
2207        0
2208    } else {
2209        std::mem::size_of::<DecompressionCtx>()
2210    }
2211}
2212
2213/// `LZ4F_getFrameInfo()` :
2214/// This function extracts frame parameters (max blockSize, dictID, etc.). Its usage is
2215/// optional: user can also invoke `LZ4F_decompress()` directly.
2216///
2217/// Extracted information will fill an existing `LZ4F_frameInfo_t` structure. This can be
2218/// useful for allocation and dictionary identification purposes.
2219///
2220/// `LZ4F_getFrameInfo()` can work in the following situations:
2221///
2222/// 1) At the beginning of a new frame, before any invocation of `LZ4F_decompress()`. It will
2223///    decode the header from `srcBuffer`, consuming the header and starting the decoding
2224///    process. Input size must be large enough to contain the full frame header. Frame
2225///    header size can be known beforehand by `LZ4F_headerSize()`. Frame header size is
2226///    variable but is guaranteed to be `>= LZ4F_HEADER_SIZE_MIN` bytes and
2227///    `<= LZ4F_HEADER_SIZE_MAX` bytes. Hence blindly providing `LZ4F_HEADER_SIZE_MAX` bytes
2228///    or more will always work. If input size is not large enough, function will fail and
2229///    return an error code.
2230///
2231/// 2) After decoding has been started, it's possible to invoke `LZ4F_getFrameInfo()` anytime
2232///    to extract already-decoded frame parameters stored within `dctx`. If decoding has
2233///    barely started and not yet read enough information to decode the header, this fails.
2234///
2235/// The number of bytes consumed from `srcBuffer` will be updated in `*srcSizePtr`. The
2236/// function only consumes bytes when decoding has not yet started, and when decoding the
2237/// header has been successful. Decompression must then resume from `(srcBuffer + *srcSizePtr)`.
2238///
2239/// - return : a hint about how many `srcSize` bytes `LZ4F_decompress()` expects for next
2240///   call, or an error code which can be tested using `LZ4F_isError()`.
2241///
2242/// note 1 : in case of error, `dctx` is not modified. Decoding operation can resume from
2243///          beginning safely.
2244/// note 2 : frame parameters are *copied into* an already-allocated `LZ4F_frameInfo_t`
2245///          structure.
2246#[no_mangle]
2247pub unsafe extern "C" fn LZ4F_getFrameInfo(
2248    ctx: LZ4FDecompressionContext,
2249    frameInfoPtr: *mut LZ4FFrameInfo,
2250    srcBuffer: *const u8,
2251    srcSizePtr: *mut size_t,
2252) -> size_t {
2253    if ctx.0.is_null() || frameInfoPtr.is_null() || srcSizePtr.is_null() {
2254        return ERROR_PARAMETER_NULL;
2255    }
2256    let inner = &mut *(ctx.0 as *mut DecompressionCtx);
2257    if inner.parsed_header {
2258        *srcSizePtr = 0;
2259        *frameInfoPtr = frame_info_from_decompression_ctx(inner);
2260        return frame_hint(inner);
2261    }
2262    if !inner.input.is_empty() {
2263        *srcSizePtr = 0;
2264        return ERROR_FRAME_DECODING_ALREADY_STARTED;
2265    }
2266    let src_size = *srcSizePtr;
2267    if srcBuffer.is_null() {
2268        *srcSizePtr = 0;
2269        return ERROR_SRC_PTR_WRONG;
2270    }
2271    if src_size < 7 {
2272        *srcSizePtr = 0;
2273        return ERROR_BAD_HEADER;
2274    }
2275    let src = slice::from_raw_parts(srcBuffer, src_size);
2276    if is_skippable_magic_prefix(src) {
2277        let Some(skip_len) = parse_skippable_frame_len(src) else {
2278            return ERROR_BAD_HEADER;
2279        };
2280        *srcSizePtr = cmp::min(skip_len, src_size);
2281        *frameInfoPtr = LZ4FFrameInfo {
2282            block_size_id: BlockSize::Default,
2283            block_mode: BlockMode::Independent,
2284            content_checksum_flag: ContentChecksum::NoChecksum,
2285            frame_type: FrameType::SkippableFrame,
2286            content_size: 0,
2287            dict_id: 0,
2288            block_checksum_flag: BlockChecksum::NoBlockChecksum,
2289        };
2290        return 0;
2291    }
2292    let (prefs, header_len) = match parse_frame_header(src) {
2293        Ok(parsed) => parsed,
2294        Err(code) => {
2295            *srcSizePtr = 0;
2296            return code;
2297        }
2298    };
2299    if !inner.parsed_header && !inner.done {
2300        apply_frame_prefs(inner, prefs);
2301    }
2302    *srcSizePtr = header_len;
2303    *frameInfoPtr = LZ4FFrameInfo {
2304        block_size_id: block_size_enum(prefs.block_size_id),
2305        block_mode: if prefs.block_independent {
2306            BlockMode::Independent
2307        } else {
2308            BlockMode::Linked
2309        },
2310        content_checksum_flag: if prefs.content_checksum {
2311            ContentChecksum::ChecksumEnabled
2312        } else {
2313            ContentChecksum::NoChecksum
2314        },
2315        frame_type: FrameType::Frame,
2316        content_size: prefs.content_size,
2317        dict_id: prefs.dict_id,
2318        block_checksum_flag: if prefs.block_checksum {
2319            BlockChecksum::BlockChecksumEnabled
2320        } else {
2321            BlockChecksum::NoBlockChecksum
2322        },
2323    };
2324    0
2325}
2326
2327/// `LZ4F_headerSize()` : v1.9.0+
2328/// Provide the header size of a frame starting at `src`. `srcSize` must be
2329/// `>= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH`, which is enough to decode the header length.
2330///
2331/// - return : size of frame header, or an error code, which can be tested using
2332///   `LZ4F_isError()`.
2333///
2334/// note : Frame header size is variable, but is guaranteed to be `>= LZ4F_HEADER_SIZE_MIN`
2335/// bytes, and `<= LZ4F_HEADER_SIZE_MAX` bytes.
2336#[no_mangle]
2337pub unsafe extern "C" fn LZ4F_headerSize(src: *const c_void, srcSize: size_t) -> size_t {
2338    if src.is_null() {
2339        return ERROR_SRC_PTR_WRONG;
2340    }
2341    if srcSize < 5 {
2342        return ERROR_BAD_HEADER;
2343    }
2344    let src = slice::from_raw_parts(src as *const u8, srcSize);
2345    if is_skippable_magic_prefix(src) {
2346        return 8;
2347    }
2348    if src[..4] != LZ4F_MAGIC {
2349        return ERROR_FRAME_TYPE_UNKNOWN;
2350    }
2351    expected_frame_header_len(src).unwrap_or(ERROR_BAD_HEADER)
2352}
2353
2354/// Incrementally decompresses an LZ4 frame into user-provided buffers.
2355///
2356/// Call repeatedly until the return value is 0 (frame fully decoded) or an error is reported.
2357/// On each call, the function consumes up to `*srcSizePtr` bytes from `srcBuffer` and
2358/// produces up to `*dstSizePtr` bytes into `dstBuffer`. It updates both size pointers with
2359/// the actual number of bytes consumed/produced. There is no separate flush step.
2360///
2361/// Typical loop:
2362/// - Provide whatever input you have and an available output buffer.
2363/// - Read how much input was consumed and how much output was produced.
2364/// - Use the returned value as a hint for how many source bytes are ideal next time.
2365///
2366/// Returns:
2367/// - `> 0` : Hint (in bytes) for how many source bytes are ideal to provide on the next
2368///   call. The current frame is not yet complete.
2369/// - `0` : The current frame is fully decoded. If `*srcSizePtr` is less than the provided
2370///   value, the unconsumed tail is the start of another frame (if any).
2371/// - error : An error code; test with `LZ4F_isError(ret)`. After an error, `dctx` is not
2372///   resumable: call `LZ4F_resetDecompressionContext()` before reusing it.
2373///
2374/// Notes:
2375/// - The function may not consume all provided input on each call. Always check `*srcSizePtr`.
2376///   Present any unconsumed source bytes again on the next call.
2377/// - `dstBuffer` content is overwritten; it does not need to be stable across calls.
2378/// - After finishing a frame (return == 0), you may immediately start feeding the next frame
2379///   into the same `dctx`.
2380///
2381/// Warning: If you called `LZ4F_getFrameInfo()` beforehand, you must advance `srcBuffer`
2382/// and decrease `*srcSizePtr` by the number of bytes it consumed (the frame header).
2383/// Failing to do so can cause decompression failure or, worse, silent corruption.
2384#[no_mangle]
2385pub unsafe extern "C" fn LZ4F_decompress(
2386    ctx: LZ4FDecompressionContext,
2387    dstBuffer: *mut u8,
2388    dstSizePtr: *mut size_t,
2389    srcBuffer: *const u8,
2390    srcSizePtr: *mut size_t,
2391    dOptPtr: *const LZ4FDecompressOptions,
2392) -> size_t {
2393    if ctx.0.is_null() || dstSizePtr.is_null() || srcSizePtr.is_null() {
2394        return ERROR_PARAMETER_NULL;
2395    }
2396    // `stable_dst` is an upstream storage optimization. This implementation
2397    // keeps linked-block history in `DecompressionCtx::dictionary`, so both
2398    // stable and unstable destination modes share the same behavior.
2399    let _stable_dst = !dOptPtr.is_null() && (*dOptPtr).stable_dst != 0;
2400    let inner = &mut *(ctx.0 as *mut DecompressionCtx);
2401    if !dOptPtr.is_null() && (*dOptPtr).skipChecksums != 0 {
2402        inner.skip_checksums = true;
2403    }
2404    let skip_checksums = inner.skip_checksums;
2405    let src_size = *srcSizePtr;
2406    let dst_capacity = *dstSizePtr;
2407    if dst_capacity > 0 && dstBuffer.is_null() {
2408        return ERROR_PARAMETER_NULL;
2409    }
2410
2411    if src_size > 0
2412        && dst_capacity > 0
2413        && !srcBuffer.is_null()
2414        && inner.input.is_empty()
2415        && pending_is_empty(inner)
2416        && inner.parsed_header
2417        && !inner.done
2418    {
2419        let src = slice::from_raw_parts(srcBuffer, src_size);
2420        if let Some(result) = try_copy_raw_block_slice_to_dst(
2421            inner,
2422            src,
2423            slice::from_raw_parts_mut(dstBuffer, dst_capacity),
2424            skip_checksums,
2425        ) {
2426            match result {
2427                Ok((consumed, written)) => {
2428                    *srcSizePtr = consumed;
2429                    *dstSizePtr = written;
2430                    if inner.done && pending_is_empty(inner) {
2431                        *inner = DecompressionCtx::default();
2432                        return 0;
2433                    }
2434                    return frame_hint(inner);
2435                }
2436                Err(code) => return code,
2437            }
2438        }
2439        if let Some(result) = try_decompress_frame_block_slice_to_dst(
2440            inner,
2441            src,
2442            slice::from_raw_parts_mut(dstBuffer, dst_capacity),
2443            skip_checksums,
2444        ) {
2445            match result {
2446                Ok((consumed, written)) => {
2447                    *srcSizePtr = consumed;
2448                    *dstSizePtr = written;
2449                    if inner.done && pending_is_empty(inner) {
2450                        *inner = DecompressionCtx::default();
2451                        return 0;
2452                    }
2453                    return frame_hint(inner);
2454                }
2455                Err(code) => return code,
2456            }
2457        }
2458    }
2459
2460    if src_size > 0 {
2461        if srcBuffer.is_null() {
2462            return ERROR_SRC_PTR_WRONG;
2463        }
2464        inner
2465            .input
2466            .extend_from_slice(slice::from_raw_parts(srcBuffer, src_size));
2467    }
2468
2469    if pending_is_empty(inner) && dst_capacity > 0 {
2470        if let Err(code) = parse_frame_header_if_available(inner) {
2471            return code;
2472        }
2473        // Drain as many complete blocks from `inner.input` as fit in `dst`
2474        // within this single LZ4F_decompress call. Without this loop, each
2475        // call processed exactly one block while accumulating leftover input
2476        // — so once the slow path ran for any reason (e.g. the first call,
2477        // before the header is parsed), `inner.input` was never drained and
2478        // the fast slice-to-dst path could not fire on subsequent calls.
2479        let dst_slice = slice::from_raw_parts_mut(dstBuffer, dst_capacity);
2480        let mut total_written = 0usize;
2481        loop {
2482            let remaining = &mut dst_slice[total_written..];
2483            if remaining.is_empty() {
2484                break;
2485            }
2486            if inner.raw_block_remaining > 0 {
2487                match copy_raw_block_from_input_to_dst(inner, remaining, skip_checksums) {
2488                    Some(Ok(written)) => {
2489                        total_written += written;
2490                        if inner.done {
2491                            break;
2492                        }
2493                        if written == 0 {
2494                            break;
2495                        }
2496                        continue;
2497                    }
2498                    Some(Err(code)) => return code,
2499                    None => break,
2500                }
2501            }
2502            match try_decompress_frame_block_to_dst(inner, remaining, skip_checksums) {
2503                Some(Ok(written)) => {
2504                    total_written += written;
2505                    if inner.done {
2506                        break;
2507                    }
2508                    // If the most recent call wrote 0 bytes (e.g. final
2509                    // block-zero marker followed by trailer) we still made
2510                    // progress on inner state — try once more and let the
2511                    // function return None on the next call when truly out
2512                    // of complete blocks.
2513                    continue;
2514                }
2515                Some(Err(code)) => return code,
2516                None => break,
2517            }
2518        }
2519        if total_written > 0 || inner.done {
2520            let consumed = consumed_from_call(inner.done, src_size, inner.input.len());
2521            *srcSizePtr = consumed;
2522            *dstSizePtr = total_written;
2523            if inner.done && pending_is_empty(inner) {
2524                *inner = DecompressionCtx::default();
2525                return 0;
2526            }
2527            return frame_hint(inner);
2528        }
2529    }
2530
2531    if let Err(code) = parse_available_frame(inner, skip_checksums) {
2532        return code;
2533    }
2534    let pending_len = pending_len(inner);
2535    let to_copy = cmp::min(dst_capacity, pending_len);
2536    if to_copy > 0 {
2537        ptr::copy_nonoverlapping(
2538            inner.pending.as_ptr().add(inner.pending_pos),
2539            dstBuffer,
2540            to_copy,
2541        );
2542        inner.pending_pos += to_copy;
2543        if inner.pending_pos == inner.pending.len() {
2544            inner.pending.clear();
2545            inner.pending_pos = 0;
2546        }
2547    }
2548    *dstSizePtr = to_copy;
2549    if pending_is_empty(inner) && !inner.done {
2550        if let Err(code) = parse_available_frame(inner, skip_checksums) {
2551            return code;
2552        }
2553    }
2554    *srcSizePtr = consumed_from_call(inner.done, src_size, inner.input.len());
2555    if inner.done && pending_is_empty(inner) {
2556        *inner = DecompressionCtx::default();
2557        return 0;
2558    }
2559    frame_hint(inner)
2560}
2561
2562/// `LZ4F_decompress_usingDict()` : stable since v1.10
2563/// Same as `LZ4F_decompress()`, using a predefined dictionary. Dictionary is used "in place",
2564/// without any preprocessing. It must remain accessible throughout the entire frame decoding.
2565#[no_mangle]
2566pub unsafe extern "C" fn LZ4F_decompress_usingDict(
2567    ctx: LZ4FDecompressionContext,
2568    dstBuffer: *mut c_void,
2569    dstSizePtr: *mut size_t,
2570    srcBuffer: *const c_void,
2571    srcSizePtr: *mut size_t,
2572    dict: *const c_void,
2573    dictSize: size_t,
2574    decompressOptionsPtr: *const LZ4FDecompressOptions,
2575) -> size_t {
2576    if ctx.0.is_null() {
2577        return ERROR_PARAMETER_NULL;
2578    }
2579    if dictSize > 0 && dict.is_null() {
2580        return ERROR_SRC_PTR_WRONG;
2581    }
2582    let inner = &mut *(ctx.0 as *mut DecompressionCtx);
2583    if !inner.parsed_header && dictSize > 0 {
2584        let dict = slice::from_raw_parts(dict as *const u8, dictSize);
2585        let keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
2586        inner.dictionary.clear();
2587        inner
2588            .dictionary
2589            .extend_from_slice(&dict[dict.len() - keep..]);
2590        inner.external_dictionary = true;
2591    }
2592    LZ4F_decompress(
2593        ctx,
2594        dstBuffer as *mut u8,
2595        dstSizePtr,
2596        srcBuffer as *const u8,
2597        srcSizePtr,
2598        decompressOptionsPtr,
2599    )
2600}
2601
2602/// `LZ4F_resetDecompressionContext()` : added in v1.8.0
2603/// In case of an error, the context is left in "undefined" state. In which case, it's
2604/// necessary to reset it, before re-using it. This method can also be used to abruptly stop
2605/// any unfinished decompression, and start a new one using same context resources. Always
2606/// successful.
2607#[no_mangle]
2608pub unsafe extern "C" fn LZ4F_resetDecompressionContext(ctx: LZ4FDecompressionContext) {
2609    if !ctx.0.is_null() {
2610        *(ctx.0 as *mut DecompressionCtx) = DecompressionCtx::default();
2611    }
2612}
2613
2614/// Allocate and initialize an `LZ4_stream_t` streaming compression state.
2615///
2616/// A newly created state is automatically initialized. The same state can be
2617/// re-used multiple times consecutively, starting each new stream of blocks
2618/// with `LZ4_resetStream_fast()`. Release with `LZ4_freeStream()`.
2619#[no_mangle]
2620pub unsafe extern "C" fn LZ4_createStream() -> *mut LZ4StreamEncode {
2621    Box::into_raw(Box::<EncodeStreamCtx>::default()) as *mut LZ4StreamEncode
2622}
2623
2624/// Release a streaming compression state previously allocated with
2625/// `LZ4_createStream()`.
2626///
2627/// Returns 0; passing NULL is safe and treated as a no-op.
2628#[no_mangle]
2629pub unsafe extern "C" fn LZ4_freeStream(stream: *mut LZ4StreamEncode) -> c_int {
2630    if !stream.is_null() {
2631        drop(Box::from_raw(stream as *mut EncodeStreamCtx));
2632    }
2633    0
2634}
2635
2636/// Deprecated wrapper for streaming compression; use
2637/// `LZ4_compress_fast_continue()` instead.
2638///
2639/// Compresses `src` into `dst` using data from previously compressed blocks
2640/// as dictionary, with default acceleration. Returns the size of the
2641/// compressed block, or 0 on error (typically, output does not fit into
2642/// `dst`).
2643#[no_mangle]
2644pub unsafe extern "C" fn LZ4_compress_continue(
2645    stream: *mut LZ4StreamEncode,
2646    src: *const c_char,
2647    dst: *mut c_char,
2648    srcSize: c_int,
2649    dstCapacity: c_int,
2650) -> c_int {
2651    if stream.is_null() || srcSize < 0 || dstCapacity <= 0 || src.is_null() || dst.is_null() {
2652        return 0;
2653    }
2654    let ctx = &mut *(stream as *mut EncodeStreamCtx);
2655    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
2656    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, dstCapacity as usize);
2657    let written = if ctx.dictionary.is_empty() {
2658        compress_block(src_slice, dst_slice, 1)
2659    } else {
2660        compress_block_with_dict(src_slice, dst_slice, &ctx.dictionary, 1)
2661    }
2662    .map_or(0, |n| n as c_int);
2663    if written > 0 {
2664        if ctx.attached_dictionary {
2665            ctx.dictionary.clear();
2666            append_hc_dictionary(&mut ctx.dictionary, src_slice);
2667            ctx.attached_dictionary = false;
2668        } else {
2669            append_hc_dictionary(&mut ctx.dictionary, src_slice);
2670        }
2671    }
2672    written
2673}
2674
2675/// Compress `src` content using data from previously compressed blocks, for
2676/// better compression ratio.
2677///
2678/// `dst` buffer must be already allocated. If `dstCapacity >=
2679/// LZ4_compressBound(srcSize)`, compression is guaranteed to succeed, and
2680/// runs faster.
2681///
2682/// Returns the size of the compressed block, or 0 if there is an error
2683/// (typically, cannot fit into `dst`).
2684///
2685/// Note 1: Each invocation generates a new block; each block has precise
2686/// boundaries and must be decompressed separately.
2687///
2688/// Note 2: The previous 64KB of source data is assumed to remain present,
2689/// unmodified, at the same address in memory.
2690///
2691/// Note 3: When input is structured as a double-buffer, each buffer can have
2692/// any size, including < 64 KB. Make sure buffers are separated by at least
2693/// one byte.
2694///
2695/// Note 4: If input buffer is a ring-buffer, it can have any size, including
2696/// < 64 KB.
2697///
2698/// Note 5: After an error, the stream status is undefined (invalid); it can
2699/// only be reset or freed.
2700#[no_mangle]
2701pub unsafe extern "C" fn LZ4_compress_fast_continue(
2702    stream: *mut LZ4StreamEncode,
2703    src: *const c_char,
2704    dst: *mut c_char,
2705    srcSize: c_int,
2706    dstCapacity: c_int,
2707    acceleration: c_int,
2708) -> c_int {
2709    if stream.is_null() || srcSize < 0 || dstCapacity <= 0 || src.is_null() || dst.is_null() {
2710        return 0;
2711    }
2712    let ctx = &mut *(stream as *mut EncodeStreamCtx);
2713    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
2714    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, dstCapacity as usize);
2715    let acceleration = normalize_acceleration(acceleration);
2716    let written = if ctx.dictionary.is_empty() {
2717        compress_block(src_slice, dst_slice, acceleration)
2718    } else {
2719        compress_block_with_dict(src_slice, dst_slice, &ctx.dictionary, acceleration)
2720    }
2721    .map_or(0, |n| n as c_int);
2722    if written > 0 {
2723        if ctx.attached_dictionary {
2724            ctx.dictionary.clear();
2725            append_hc_dictionary(&mut ctx.dictionary, src_slice);
2726            ctx.attached_dictionary = false;
2727        } else {
2728            append_hc_dictionary(&mut ctx.dictionary, src_slice);
2729        }
2730    }
2731    written
2732}
2733
2734/// Deprecated wrapper for streaming compression with output size limit; use
2735/// `LZ4_compress_fast_continue()` instead.
2736#[no_mangle]
2737pub unsafe extern "C" fn LZ4_compress_limitedOutput_continue(
2738    stream: *mut LZ4StreamEncode,
2739    src: *const c_char,
2740    dst: *mut c_char,
2741    srcSize: c_int,
2742    maxOutputSize: c_int,
2743) -> c_int {
2744    LZ4_compress_continue(stream, src, dst, srcSize, maxOutputSize)
2745}
2746
2747/// Prepare an `LZ4_stream_t` for a new chain of dependent blocks
2748/// (e.g., `LZ4_compress_fast_continue()`).
2749///
2750/// An `LZ4_stream_t` must be initialized once before use. This is
2751/// automatically done when created by `LZ4_createStream()`. However, when
2752/// the structure is simply declared on stack, `LZ4_initStream()` must be
2753/// used first.
2754///
2755/// After init, start any new stream with `LZ4_resetStream_fast()`. The same
2756/// `LZ4_stream_t` can be re-used multiple times to compress multiple
2757/// streams, provided each new stream starts with `LZ4_resetStream_fast()`.
2758///
2759/// `LZ4_resetStream_fast()` is much faster than `LZ4_initStream()`, but is
2760/// not compatible with memory regions containing garbage data.
2761///
2762/// Note: it's only useful to call `LZ4_resetStream_fast()` in the context
2763/// of streaming compression. The `*extState*` functions perform their own
2764/// resets.
2765#[no_mangle]
2766pub unsafe extern "C" fn LZ4_resetStream_fast(stream: *mut LZ4StreamEncode) {
2767    if !stream.is_null() {
2768        let ctx = &mut *(stream as *mut EncodeStreamCtx);
2769        ctx.dictionary.clear();
2770        ctx.attached_dictionary = false;
2771    }
2772}
2773
2774/// Initialize an `LZ4_stream_t` structure.
2775///
2776/// An `LZ4_stream_t` structure must be initialized at least once; this is
2777/// done with `LZ4_initStream()` or `LZ4_resetStream()`. Consider switching
2778/// to `LZ4_initStream()`; `LZ4_resetStream()` will trigger deprecation
2779/// warnings in a future version.
2780#[no_mangle]
2781pub unsafe extern "C" fn LZ4_resetStream(stream: *mut LZ4StreamEncode) {
2782    LZ4_resetStream_fast(stream)
2783}
2784
2785/// Properly initialize a newly declared `LZ4_stream_t`.
2786///
2787/// An `LZ4_stream_t` structure must be initialized at least once. This is
2788/// automatically done when invoking `LZ4_createStream()`, but it's not done
2789/// when the structure is simply declared on stack.
2790///
2791/// `LZ4_initStream()` can also initialize any arbitrary buffer of sufficient
2792/// size, and will return a pointer of proper type upon initialization.
2793///
2794/// Initialization fails (returns NULL) if size and alignment conditions are
2795/// not respected. An `LZ4_stream_t` structure guarantees correct alignment
2796/// and size.
2797#[no_mangle]
2798pub unsafe extern "C" fn LZ4_initStream(
2799    stateBuffer: *mut c_void,
2800    size: size_t,
2801) -> *mut LZ4StreamEncode {
2802    if stateBuffer.is_null() {
2803        return ptr::null_mut();
2804    }
2805    if size < LZ4_sizeofStreamState() as usize {
2806        return ptr::null_mut();
2807    }
2808    if !(stateBuffer as usize).is_multiple_of(std::mem::align_of::<EncodeStreamCtx>()) {
2809        return ptr::null_mut();
2810    }
2811    ptr::write(
2812        stateBuffer as *mut EncodeStreamCtx,
2813        EncodeStreamCtx::default(),
2814    );
2815    stateBuffer as *mut LZ4StreamEncode
2816}
2817
2818/// Deprecated obsolete streaming constructor; use `LZ4_createStream()`
2819/// instead.
2820///
2821/// Preserved for ABI compatibility. The `inputBuffer` argument is ignored;
2822/// history is no longer tracked through this entry point.
2823#[no_mangle]
2824pub unsafe extern "C" fn LZ4_create(_inputBuffer: *mut c_char) -> *mut c_void {
2825    LZ4_createStream() as *mut c_void
2826}
2827
2828/// Deprecated: use `LZ4_createStream()` instead.
2829///
2830/// Returns the size in bytes of an `LZ4_stream_t` state buffer.
2831#[no_mangle]
2832pub extern "C" fn LZ4_sizeofStreamState() -> c_int {
2833    LZ4_sizeofState()
2834}
2835
2836/// Deprecated: use `LZ4_resetStream()` instead.
2837///
2838/// Resets a user-provided state buffer; the `inputBuffer` argument is
2839/// ignored. Returns 0 on success, non-zero if `state` is NULL.
2840#[no_mangle]
2841pub unsafe extern "C" fn LZ4_resetStreamState(
2842    state: *mut c_void,
2843    _inputBuffer: *mut c_char,
2844) -> c_int {
2845    if state.is_null() {
2846        return 1;
2847    }
2848    ptr::write(state as *mut EncodeStreamCtx, EncodeStreamCtx::default());
2849    0
2850}
2851
2852/// Deprecated: use `LZ4_saveDict()` instead.
2853///
2854/// Obsolete streaming helper preserved for ABI compatibility; returns a
2855/// pointer to the dictionary tail when present, NULL otherwise.
2856#[no_mangle]
2857pub unsafe extern "C" fn LZ4_slideInputBuffer(state: *mut c_void) -> *mut c_char {
2858    if state.is_null() {
2859        return ptr::null_mut();
2860    }
2861    let ctx = &mut *(state as *mut EncodeStreamCtx);
2862    if ctx.dictionary.is_empty() {
2863        ptr::null_mut()
2864    } else {
2865        ctx.dictionary.as_mut_ptr() as *mut c_char
2866    }
2867}
2868
2869/// Attach a static dictionary stream to a working stream for efficient
2870/// re-use.
2871///
2872/// Rather than re-loading the dictionary buffer into a working context
2873/// before each compression, this function introduces a no-copy setup
2874/// mechanism in which the working stream references `dictionaryStream`
2875/// in-place.
2876///
2877/// Only states which have been prepared by `LZ4_loadDict()` or
2878/// `LZ4_loadDictSlow()` should be expected to work as the dictionary
2879/// stream. Alternatively, `dictionaryStream` may be NULL, in which case
2880/// any existing dictionary stream is unset.
2881///
2882/// If a dictionary is provided, it replaces any pre-existing stream
2883/// history. The dictionary contents are the only history that can be
2884/// referenced and logically immediately precede the data compressed in
2885/// the first subsequent compression call.
2886///
2887/// The dictionary will only remain attached through the first compression
2888/// call, at the end of which it is cleared. `dictionaryStream` (and its
2889/// source buffer) must remain in-place / accessible / unchanged through
2890/// the completion of the compression session.
2891#[no_mangle]
2892pub unsafe extern "C" fn LZ4_attach_dictionary(
2893    workingStream: *mut LZ4StreamEncode,
2894    dictionaryStream: *const LZ4StreamEncode,
2895) {
2896    if workingStream.is_null() {
2897        return;
2898    }
2899    let working = &mut *(workingStream as *mut EncodeStreamCtx);
2900    working.dictionary.clear();
2901    working.attached_dictionary = false;
2902    if !dictionaryStream.is_null() {
2903        let dictionary = &*(dictionaryStream as *const EncodeStreamCtx);
2904        working.dictionary.extend_from_slice(&dictionary.dictionary);
2905        working.attached_dictionary = !working.dictionary.is_empty();
2906    }
2907}
2908
2909/// Reference a static dictionary into an `LZ4_stream_t`.
2910///
2911/// The dictionary must remain available during compression.
2912/// `LZ4_loadDict()` triggers a reset, so any previous data will be
2913/// forgotten. The same dictionary must be loaded on the decompression side
2914/// for successful decoding. Dictionaries are useful for better compression
2915/// of small data (KB range). Loading a size of 0 is allowed and is the
2916/// same as a reset.
2917///
2918/// Returns the loaded dictionary size, in bytes (only the last 64 KB are
2919/// loaded).
2920#[no_mangle]
2921pub unsafe extern "C" fn LZ4_loadDict(
2922    stream: *mut LZ4StreamEncode,
2923    dictionary: *const c_char,
2924    dictSize: c_int,
2925) -> c_int {
2926    if stream.is_null() || dictSize < 0 || (dictSize > 0 && dictionary.is_null()) {
2927        return 0;
2928    }
2929    let ctx = &mut *(stream as *mut EncodeStreamCtx);
2930    ctx.dictionary.clear();
2931    ctx.attached_dictionary = false;
2932    if dictSize as usize >= HASH_UNIT {
2933        let dict = slice::from_raw_parts(dictionary as *const u8, dictSize as usize);
2934        let keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
2935        ctx.dictionary.extend_from_slice(&dict[dict.len() - keep..]);
2936    }
2937    ctx.dictionary.len() as c_int
2938}
2939
2940/// Same as `LZ4_loadDict()`, but uses a bit more CPU to reference the
2941/// dictionary content more thoroughly.
2942///
2943/// This is expected to slightly improve compression ratio. The extra-CPU
2944/// cost is likely worth it if the dictionary is re-used across multiple
2945/// sessions.
2946///
2947/// Returns the loaded dictionary size, in bytes (only the last 64 KB are
2948/// loaded).
2949#[no_mangle]
2950pub unsafe extern "C" fn LZ4_loadDictSlow(
2951    stream: *mut LZ4StreamEncode,
2952    dictionary: *const c_char,
2953    dictSize: c_int,
2954) -> c_int {
2955    LZ4_loadDict(stream, dictionary, dictSize)
2956}
2957
2958/// Save the last 64 KB of stream history into a safe buffer.
2959///
2960/// If the last 64 KB of data cannot be guaranteed to remain available at
2961/// its current memory location, save it into a safer place (`safeBuffer`).
2962/// This is schematically equivalent to a `memcpy()` followed by
2963/// `LZ4_loadDict()`, but is much faster because `LZ4_saveDict()` doesn't
2964/// need to rebuild tables.
2965///
2966/// Returns the saved dictionary size in bytes (necessarily <=
2967/// `maxDictSize`), or 0 on error.
2968#[no_mangle]
2969pub unsafe extern "C" fn LZ4_saveDict(
2970    stream: *mut LZ4StreamEncode,
2971    safeBuffer: *mut c_char,
2972    maxDictSize: c_int,
2973) -> c_int {
2974    if stream.is_null() || maxDictSize < 0 || (maxDictSize > 0 && safeBuffer.is_null()) {
2975        return 0;
2976    }
2977    let ctx = &mut *(stream as *mut EncodeStreamCtx);
2978    let keep = cmp::min(
2979        ctx.dictionary.len(),
2980        cmp::min(maxDictSize as usize, LZ4_DISTANCE_MAX),
2981    );
2982    if keep > 0 {
2983        ptr::copy_nonoverlapping(
2984            ctx.dictionary[ctx.dictionary.len() - keep..].as_ptr(),
2985            safeBuffer as *mut u8,
2986            keep,
2987        );
2988        ctx.dictionary = ctx.dictionary[ctx.dictionary.len() - keep..].to_vec();
2989        ctx.attached_dictionary = false;
2990    } else {
2991        ctx.dictionary.clear();
2992        ctx.attached_dictionary = false;
2993    }
2994    keep as c_int
2995}
2996
2997/// Allocate memory for an LZ4 HC streaming state and initialize it.
2998///
2999/// Newly created states are automatically initialized. The same state can
3000/// be used multiple times consecutively, starting with
3001/// `LZ4_resetStreamHC_fast()` to start a new stream of blocks. Release with
3002/// `LZ4_freeStreamHC()`.
3003#[no_mangle]
3004pub unsafe extern "C" fn LZ4_createStreamHC() -> *mut LZ4StreamHC {
3005    Box::into_raw(Box::<HcStreamCtx>::default()) as *mut LZ4StreamHC
3006}
3007
3008/// Release an LZ4 HC streaming state previously allocated with
3009/// `LZ4_createStreamHC()`.
3010///
3011/// Returns 0; passing NULL is safe and treated as a no-op.
3012#[no_mangle]
3013pub unsafe extern "C" fn LZ4_freeStreamHC(stream: *mut LZ4StreamHC) -> c_int {
3014    if !stream.is_null() {
3015        drop(Box::from_raw(stream as *mut HcStreamCtx));
3016    }
3017    0
3018}
3019
3020/// Quickly prepare an `LZ4_streamHC_t` for a new compression.
3021///
3022/// When an `LZ4_streamHC_t` is known to be in an internally coherent state,
3023/// it can often be prepared for a new compression with almost no work,
3024/// only sometimes falling back to the full reset performed by
3025/// `LZ4_resetStreamHC()`.
3026///
3027/// `LZ4_streamHC_t`s are guaranteed to be in a valid state when returned
3028/// from `LZ4_createStreamHC()`, reset by `LZ4_resetStreamHC()`, after a
3029/// `memset` to zero, or after a successful compression call.
3030///
3031/// A stream that was last used in a compression call that returned an
3032/// error may be passed to this function; however, it will be fully reset,
3033/// which will clear any existing history and settings.
3034#[no_mangle]
3035pub unsafe extern "C" fn LZ4_resetStreamHC_fast(stream: *mut LZ4StreamHC, compressionLevel: c_int) {
3036    if !stream.is_null() {
3037        let ctx = &mut *(stream as *mut HcStreamCtx);
3038        ctx.compression_level = normalize_hc_level(compressionLevel);
3039        ctx.dictionary.clear();
3040        ctx.attached_dictionary = false;
3041    }
3042}
3043
3044/// Reset an `LZ4_streamHC_t` to a clean state with the given compression
3045/// level.
3046///
3047/// Replaced by `LZ4_initStreamHC()`. The intention is to emphasize the
3048/// difference with `LZ4_resetStreamHC_fast()`, which is now the
3049/// recommended function to start a new stream of blocks but cannot be used
3050/// to initialize a memory segment containing arbitrary garbage data. It is
3051/// recommended to switch to `LZ4_initStreamHC()`; `LZ4_resetStreamHC()`
3052/// will generate deprecation warnings in a future version.
3053#[no_mangle]
3054pub unsafe extern "C" fn LZ4_resetStreamHC(stream: *mut LZ4StreamHC, compressionLevel: c_int) {
3055    if !stream.is_null() {
3056        let ctx = &mut *(stream as *mut HcStreamCtx);
3057        *ctx = HcStreamCtx::default();
3058        ctx.compression_level = normalize_hc_level(compressionLevel);
3059    }
3060}
3061
3062/// Initialize an `LZ4_streamHC_t` placed in a user-provided buffer.
3063///
3064/// Required before first use of a statically allocated `LZ4_streamHC_t`.
3065/// Returns NULL if size or alignment requirements are not met. Before
3066/// v1.9.0, use `LZ4_resetStreamHC()` instead.
3067#[no_mangle]
3068pub unsafe extern "C" fn LZ4_initStreamHC(buffer: *mut c_void, size: size_t) -> *mut LZ4StreamHC {
3069    if buffer.is_null() {
3070        return ptr::null_mut();
3071    }
3072    if size < LZ4_sizeofStreamStateHC() as usize {
3073        return ptr::null_mut();
3074    }
3075    if !(buffer as usize).is_multiple_of(std::mem::align_of::<HcStreamCtx>()) {
3076        return ptr::null_mut();
3077    }
3078    ptr::write(buffer as *mut HcStreamCtx, HcStreamCtx::default());
3079    buffer as *mut LZ4StreamHC
3080}
3081
3082/// Change compression level between successive invocations of
3083/// `LZ4_compress_HC_continue*()` for dynamic adaptation.
3084#[no_mangle]
3085pub unsafe extern "C" fn LZ4_setCompressionLevel(
3086    stream: *mut LZ4StreamHC,
3087    compressionLevel: c_int,
3088) {
3089    if !stream.is_null() {
3090        (*(stream as *mut HcStreamCtx)).compression_level = normalize_hc_level(compressionLevel);
3091    }
3092}
3093
3094/// Make the optimal parser favor decompression speed over compression
3095/// ratio.
3096///
3097/// Only applicable to compression levels >= `LZ4HC_CLEVEL_OPT_MIN`.
3098#[no_mangle]
3099pub unsafe extern "C" fn LZ4_favorDecompressionSpeed(stream: *mut LZ4StreamHC, favor: c_int) {
3100    if !stream.is_null() {
3101        (*(stream as *mut HcStreamCtx)).favor_dec_speed = favor != 0;
3102    }
3103}
3104
3105/// Attach a static HC dictionary stream to a working stream for efficient
3106/// re-use across many compressions.
3107///
3108/// Rather than re-loading the dictionary buffer into a working context
3109/// before each compression, this function introduces a no-copy setup
3110/// mechanism in which the working stream references the dictionary stream
3111/// in-place. Only streams which have been prepared by `LZ4_loadDictHC()`
3112/// should be expected to work as the dictionary stream.
3113///
3114/// Alternatively, `dictionary_stream` may be NULL, in which case any
3115/// existing dictionary stream is unset.
3116///
3117/// A dictionary should only be attached to a stream without any history
3118/// (i.e., a stream that has just been reset). The dictionary will remain
3119/// attached to the working stream only for the current stream session;
3120/// calls to `LZ4_resetStreamHC(_fast)` will remove the dictionary context
3121/// association. The dictionary stream (and source buffer) must remain
3122/// in-place / accessible / unchanged through the lifetime of the stream
3123/// session.
3124#[no_mangle]
3125pub unsafe extern "C" fn LZ4_attach_HC_dictionary(
3126    working_stream: *mut LZ4StreamHC,
3127    dictionary_stream: *const LZ4StreamHC,
3128) {
3129    if working_stream.is_null() {
3130        return;
3131    }
3132    let working = &mut *(working_stream as *mut HcStreamCtx);
3133    working.dictionary.clear();
3134    working.attached_dictionary = false;
3135    if !dictionary_stream.is_null() {
3136        let dictionary = &*(dictionary_stream as *const HcStreamCtx);
3137        working.dictionary.extend_from_slice(&dictionary.dictionary);
3138        working.attached_dictionary = !working.dictionary.is_empty();
3139    }
3140}
3141
3142/// Load an initial "fictional block" as a dictionary into an HC stream.
3143///
3144/// Note: In order for `LZ4_loadDictHC()` to create the correct data
3145/// structures, it is essential to set the compression level _before_
3146/// loading the dictionary. Only the last 64 KB of the dictionary are kept.
3147#[no_mangle]
3148pub unsafe extern "C" fn LZ4_loadDictHC(
3149    stream: *mut LZ4StreamHC,
3150    dictionary: *const c_char,
3151    dictSize: c_int,
3152) -> c_int {
3153    if stream.is_null() || dictSize < 0 || (dictSize > 0 && dictionary.is_null()) {
3154        return 0;
3155    }
3156    let ctx = &mut *(stream as *mut HcStreamCtx);
3157    let keep = cmp::min(dictSize as usize, LZ4_DISTANCE_MAX);
3158    ctx.dictionary.clear();
3159    ctx.attached_dictionary = false;
3160    if keep > 0 {
3161        let dict = slice::from_raw_parts(dictionary as *const u8, dictSize as usize);
3162        ctx.dictionary.extend_from_slice(&dict[dict.len() - keep..]);
3163    }
3164    keep as c_int
3165}
3166
3167/// Save HC stream history content (up to 64 KB) into a user-provided
3168/// buffer.
3169///
3170/// Returns the size of the dictionary effectively saved (<= MIN(maxDictSize,
3171/// 64 KB)). This function is always successful and expects its arguments to
3172/// be valid. Note that `(NULL, 0)` is valid but `(NULL, !0)` is not, and
3173/// will result in a segfault (or assertion failure).
3174#[no_mangle]
3175pub unsafe extern "C" fn LZ4_saveDictHC(
3176    stream: *mut LZ4StreamHC,
3177    safeBuffer: *mut c_char,
3178    maxDictSize: c_int,
3179) -> c_int {
3180    if stream.is_null() || maxDictSize < 0 || (maxDictSize > 0 && safeBuffer.is_null()) {
3181        return 0;
3182    }
3183    let ctx = &mut *(stream as *mut HcStreamCtx);
3184    if maxDictSize < 4 {
3185        ctx.dictionary.clear();
3186        ctx.attached_dictionary = false;
3187        return 0;
3188    }
3189    let keep = cmp::min(
3190        ctx.dictionary.len(),
3191        cmp::min(maxDictSize as usize, LZ4_DISTANCE_MAX),
3192    );
3193    if keep > 0 {
3194        ptr::copy_nonoverlapping(
3195            ctx.dictionary[ctx.dictionary.len() - keep..].as_ptr(),
3196            safeBuffer as *mut u8,
3197            keep,
3198        );
3199        ctx.dictionary = ctx.dictionary[ctx.dictionary.len() - keep..].to_vec();
3200        ctx.attached_dictionary = false;
3201    } else {
3202        ctx.dictionary.clear();
3203        ctx.attached_dictionary = false;
3204    }
3205    keep as c_int
3206}
3207
3208/// Compress a block of data using LZ4-HC streaming, using previous blocks
3209/// as the dictionary.
3210///
3211/// Compresses successive blocks of any size; previous blocks (up to 64 KB)
3212/// are used as a dictionary to improve compression ratio. Previous input
3213/// blocks (and the initial dictionary, if any) must remain accessible and
3214/// unmodified during compression.
3215///
3216/// `dst` should be sized to handle worst-case scenarios (see
3217/// `LZ4_compressBound()`, which ensures compression success). On failure
3218/// the state must be reset; the API does not guarantee recovery.
3219/// To ensure success when `dst` cannot be sized to `LZ4_compressBound()`,
3220/// consider using `LZ4_compress_HC_continue_destSize()`.
3221#[no_mangle]
3222pub unsafe extern "C" fn LZ4_compress_HC_continue(
3223    stream: *mut LZ4StreamHC,
3224    src: *const c_char,
3225    dst: *mut c_char,
3226    srcSize: c_int,
3227    maxDstSize: c_int,
3228) -> c_int {
3229    if stream.is_null() {
3230        return 0;
3231    }
3232    let ctx = &mut *(stream as *mut HcStreamCtx);
3233    if srcSize < 0 || maxDstSize <= 0 || src.is_null() || dst.is_null() {
3234        return 0;
3235    }
3236    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
3237    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, maxDstSize as usize);
3238    compress_hc_stream_block(
3239        ctx,
3240        src_slice,
3241        dst_slice,
3242        ctx.compression_level,
3243        ctx.favor_dec_speed,
3244    )
3245}
3246
3247/// Internal helper: compress one block through an HC streaming context and
3248/// then append the just-compressed input to the context's dictionary
3249/// window. Shared by `LZ4_compress_HC_continue()` and the deprecated
3250/// `LZ4_compressHC2_*` entry points.
3251fn compress_hc_stream_block(
3252    ctx: &mut HcStreamCtx,
3253    src: &[u8],
3254    dst: &mut [u8],
3255    compression_level: c_int,
3256    favor_dec_speed: bool,
3257) -> c_int {
3258    let written = if ctx.dictionary.is_empty() {
3259        compress_block_hc(src, dst, compression_level, favor_dec_speed)
3260    } else {
3261        compress_block_hc_with_dict(
3262            src,
3263            dst,
3264            &ctx.dictionary,
3265            compression_level,
3266            favor_dec_speed,
3267        )
3268    }
3269    .map_or(0, |n| n as c_int);
3270    if written > 0 && !src.is_empty() {
3271        if ctx.attached_dictionary {
3272            ctx.dictionary.clear();
3273            append_hc_dictionary(&mut ctx.dictionary, src);
3274            ctx.attached_dictionary = false;
3275        } else {
3276            append_hc_dictionary(&mut ctx.dictionary, src);
3277        }
3278    }
3279    written
3280}
3281
3282/// Similar to `LZ4_compress_HC_continue()`, but reads as much data as
3283/// possible from `src` to fit into the `targetDstSize` budget.
3284///
3285/// Returns the number of bytes written into `dst` (necessarily <=
3286/// `targetDstSize`), or 0 if compression fails. On success `*srcSizePtr` is
3287/// updated to indicate how many bytes were read from `src`. Note that this
3288/// function may not consume the entire input.
3289#[no_mangle]
3290pub unsafe extern "C" fn LZ4_compress_HC_continue_destSize(
3291    stream: *mut LZ4StreamHC,
3292    src: *const c_char,
3293    dst: *mut c_char,
3294    srcSizePtr: *mut c_int,
3295    targetDstSize: c_int,
3296) -> c_int {
3297    if stream.is_null() {
3298        return 0;
3299    }
3300    let ctx = &mut *(stream as *mut HcStreamCtx);
3301    let original = if !srcSizePtr.is_null() {
3302        *srcSizePtr
3303    } else {
3304        0
3305    };
3306    if src.is_null()
3307        || dst.is_null()
3308        || srcSizePtr.is_null()
3309        || *srcSizePtr < 0
3310        || targetDstSize <= 0
3311    {
3312        return 0;
3313    }
3314    let src_slice = slice::from_raw_parts(src as *const u8, *srcSizePtr as usize);
3315    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, targetDstSize as usize);
3316    let result = if ctx.dictionary.is_empty() {
3317        compress_hc_dest_size(
3318            src_slice,
3319            dst_slice,
3320            ctx.compression_level,
3321            ctx.favor_dec_speed,
3322        )
3323    } else {
3324        compress_hc_dest_size_with_dict(
3325            src_slice,
3326            dst_slice,
3327            &ctx.dictionary,
3328            ctx.compression_level,
3329            ctx.favor_dec_speed,
3330        )
3331    };
3332    let Some((consumed, written_usize)) = result else {
3333        return 0;
3334    };
3335    *srcSizePtr = consumed as c_int;
3336    let written = written_usize as c_int;
3337    if written > 0 && !src.is_null() && !srcSizePtr.is_null() && *srcSizePtr > 0 {
3338        let consumed = cmp::min(*srcSizePtr, original) as usize;
3339        let src_slice = slice::from_raw_parts(src as *const u8, consumed);
3340        if ctx.attached_dictionary {
3341            ctx.dictionary.clear();
3342            append_hc_dictionary(&mut ctx.dictionary, src_slice);
3343            ctx.attached_dictionary = false;
3344        } else {
3345            append_hc_dictionary(&mut ctx.dictionary, src_slice);
3346        }
3347    }
3348    written
3349}
3350
3351/// Deprecated: use `LZ4_compress_HC_continue()` instead.
3352///
3353/// Compresses one block using HC streaming with `dst` capacity implicitly
3354/// set to `LZ4_compressBound(srcSize)`.
3355#[no_mangle]
3356pub unsafe extern "C" fn LZ4_compressHC_continue(
3357    stream: *mut LZ4StreamHC,
3358    src: *const c_char,
3359    dst: *mut c_char,
3360    srcSize: c_int,
3361) -> c_int {
3362    LZ4_compress_HC_continue(stream, src, dst, srcSize, LZ4_compressBound(srcSize))
3363}
3364
3365/// Deprecated: use `LZ4_compress_HC_continue()` instead.
3366///
3367/// Compresses one block using HC streaming, with an explicit output size
3368/// limit.
3369#[no_mangle]
3370pub unsafe extern "C" fn LZ4_compressHC_limitedOutput_continue(
3371    stream: *mut LZ4StreamHC,
3372    src: *const c_char,
3373    dst: *mut c_char,
3374    srcSize: c_int,
3375    maxDstSize: c_int,
3376) -> c_int {
3377    LZ4_compress_HC_continue(stream, src, dst, srcSize, maxDstSize)
3378}
3379
3380/// Deprecated: use `LZ4_compress_HC_continue()` instead.
3381///
3382/// Compresses one block using HC streaming with an explicit compression
3383/// level; `dst` capacity is implicitly set to `LZ4_compressBound(srcSize)`.
3384#[no_mangle]
3385pub unsafe extern "C" fn LZ4_compressHC2_continue(
3386    stream: *mut c_void,
3387    src: *const c_char,
3388    dst: *mut c_char,
3389    srcSize: c_int,
3390    cLevel: c_int,
3391) -> c_int {
3392    if stream.is_null() {
3393        return 0;
3394    }
3395    if srcSize < 0 || src.is_null() || dst.is_null() {
3396        return 0;
3397    }
3398    let bound = LZ4_compressBound(srcSize);
3399    if bound <= 0 {
3400        return 0;
3401    }
3402    let ctx = &mut *(stream as *mut HcStreamCtx);
3403    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
3404    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, bound as usize);
3405    compress_hc_stream_block(
3406        ctx,
3407        src_slice,
3408        dst_slice,
3409        normalize_hc_level(cLevel),
3410        ctx.favor_dec_speed,
3411    )
3412}
3413
3414/// Deprecated: use `LZ4_compress_HC_continue()` instead.
3415///
3416/// Compresses one block using HC streaming with an explicit compression
3417/// level and an output size limit.
3418#[no_mangle]
3419pub unsafe extern "C" fn LZ4_compressHC2_limitedOutput_continue(
3420    stream: *mut c_void,
3421    src: *const c_char,
3422    dst: *mut c_char,
3423    srcSize: c_int,
3424    maxDstSize: c_int,
3425    cLevel: c_int,
3426) -> c_int {
3427    if stream.is_null() {
3428        return 0;
3429    }
3430    if srcSize < 0 || maxDstSize <= 0 || src.is_null() || dst.is_null() {
3431        return 0;
3432    }
3433    let ctx = &mut *(stream as *mut HcStreamCtx);
3434    let src_slice = slice::from_raw_parts(src as *const u8, srcSize as usize);
3435    let dst_slice = slice::from_raw_parts_mut(dst as *mut u8, maxDstSize as usize);
3436    compress_hc_stream_block(
3437        ctx,
3438        src_slice,
3439        dst_slice,
3440        normalize_hc_level(cLevel),
3441        ctx.favor_dec_speed,
3442    )
3443}
3444
3445/// Deprecated: use `LZ4_createStreamHC()` instead.
3446///
3447/// Returns the size in bytes of an `LZ4_streamHC_t` state buffer.
3448#[no_mangle]
3449pub extern "C" fn LZ4_sizeofStreamStateHC() -> c_int {
3450    LZ4_sizeofStateHC()
3451}
3452
3453/// Deprecated: use `LZ4_createStreamHC()` instead.
3454///
3455/// Obsolete HC streaming constructor; the `inputBuffer` argument is
3456/// ignored and preserved only for ABI compatibility.
3457#[no_mangle]
3458pub unsafe extern "C" fn LZ4_createHC(_inputBuffer: *const c_char) -> *mut c_void {
3459    LZ4_createStreamHC() as *mut c_void
3460}
3461
3462/// Deprecated: use `LZ4_freeStreamHC()` instead.
3463#[no_mangle]
3464pub unsafe extern "C" fn LZ4_freeHC(stream: *mut c_void) -> c_int {
3465    LZ4_freeStreamHC(stream as *mut LZ4StreamHC)
3466}
3467
3468/// Deprecated: use `LZ4_saveDictHC()` instead.
3469///
3470/// Obsolete HC streaming helper preserved for ABI compatibility. As noted
3471/// in upstream, this function truncates the history of the stream rather
3472/// than preserving a window-sized chunk of history.
3473#[no_mangle]
3474pub unsafe extern "C" fn LZ4_slideInputBufferHC(stream: *mut c_void) -> *mut c_char {
3475    if stream.is_null() {
3476        return ptr::null_mut();
3477    }
3478    let ctx = &mut *(stream as *mut HcStreamCtx);
3479    if ctx.dictionary.is_empty() {
3480        ptr::null_mut()
3481    } else {
3482        let ptr = ctx.dictionary.as_mut_ptr() as *mut c_char;
3483        ctx.dictionary.clear();
3484        ctx.attached_dictionary = false;
3485        ptr
3486    }
3487}
3488
3489/// Deprecated: use `LZ4_initStreamHC()` instead.
3490///
3491/// Resets a user-provided HC state buffer; the `inputBuffer` argument is
3492/// ignored. Returns 0 on success, non-zero if `state` is NULL.
3493#[no_mangle]
3494pub unsafe extern "C" fn LZ4_resetStreamStateHC(
3495    state: *mut c_void,
3496    _inputBuffer: *mut c_char,
3497) -> c_int {
3498    if state.is_null() {
3499        return 1;
3500    }
3501    ptr::write(state as *mut HcStreamCtx, HcStreamCtx::default());
3502    0
3503}
3504
3505/// Allocate a streaming decompression tracking context.
3506///
3507/// A tracking context can be re-used multiple times. Release with
3508/// `LZ4_freeStreamDecode()`.
3509#[no_mangle]
3510pub unsafe extern "C" fn LZ4_createStreamDecode() -> *mut LZ4StreamDecode {
3511    Box::into_raw(Box::<DecodeStreamCtx>::default()) as *mut LZ4StreamDecode
3512}
3513
3514/// Release a streaming decompression context previously allocated with
3515/// `LZ4_createStreamDecode()`.
3516///
3517/// Returns 0; passing NULL is safe and treated as a no-op.
3518#[no_mangle]
3519pub unsafe extern "C" fn LZ4_freeStreamDecode(stream: *mut LZ4StreamDecode) -> c_int {
3520    if !stream.is_null() {
3521        drop(Box::from_raw(stream as *mut DecodeStreamCtx));
3522    }
3523    0
3524}
3525
3526/// Start decompression of a new stream of blocks on an
3527/// `LZ4_streamDecode_t` context.
3528///
3529/// An `LZ4_streamDecode_t` context can be allocated once and re-used
3530/// multiple times. A dictionary can optionally be set; use NULL or size 0
3531/// for a reset. The dictionary is presumed stable: it must remain
3532/// accessible and unmodified during the next decompression.
3533///
3534/// Returns 1 if OK, 0 if error.
3535#[no_mangle]
3536pub unsafe extern "C" fn LZ4_setStreamDecode(
3537    stream: *mut LZ4StreamDecode,
3538    dictionary: *const c_char,
3539    dictSize: c_int,
3540) -> c_int {
3541    if stream.is_null() || dictSize < 0 || (dictSize > 0 && dictionary.is_null()) {
3542        return 0;
3543    }
3544    let ctx = &mut *(stream as *mut DecodeStreamCtx);
3545    ctx.dictionary.clear();
3546    if dictSize > 0 {
3547        let dict = slice::from_raw_parts(dictionary as *const u8, dictSize as usize);
3548        let keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
3549        ctx.dictionary.extend_from_slice(&dict[dict.len() - keep..]);
3550    }
3551    1
3552}
3553
3554/// Decompress consecutive blocks in "streaming" mode.
3555///
3556/// The difference with the usual independent blocks is that new blocks are
3557/// allowed to find references into former blocks. A block is an
3558/// unsplittable entity and must be presented entirely to the
3559/// decompression function. `LZ4_decompress_safe_continue()` only accepts
3560/// one block at a time. It is modeled after `LZ4_decompress_safe()` and
3561/// behaves similarly.
3562///
3563/// Returns the number of bytes decompressed into the destination buffer
3564/// (necessarily <= `dstCapacity`). If the destination buffer is not large
3565/// enough, decoding stops and a negative value is returned. If the source
3566/// stream is detected malformed, the function stops decoding and returns
3567/// a negative result.
3568///
3569/// The last 64 KB of previously decoded data _must_ remain available and
3570/// unmodified at the memory position where they were previously decoded.
3571/// If less than 64 KB has been decoded so far, all of it must be present.
3572///
3573/// If a ring buffer is used on the decompression side, see upstream
3574/// `LZ4_decoderRingBufferSize()` documentation for the supported ring
3575/// buffer arrangements. Whenever these conditions cannot be met, save the
3576/// last 64 KB of decoded data into a safe buffer and pass it to
3577/// `LZ4_setStreamDecode()` before decompressing the next block.
3578#[no_mangle]
3579pub unsafe extern "C" fn LZ4_decompress_safe_continue(
3580    stream: *mut LZ4StreamDecode,
3581    src: *const c_char,
3582    dst: *mut c_char,
3583    srcSize: c_int,
3584    dstCapacity: c_int,
3585) -> c_int {
3586    if stream.is_null() || srcSize < 0 || dstCapacity < 0 || src.is_null() || dst.is_null() {
3587        return -1;
3588    }
3589    let ctx = &mut *(stream as *mut DecodeStreamCtx);
3590    let src = slice::from_raw_parts(src as *const u8, srcSize as usize);
3591    let dst = slice::from_raw_parts_mut(dst as *mut u8, dstCapacity as usize);
3592    match decompress_block_with_dict(src, dst, &ctx.dictionary) {
3593        Some(n) => {
3594            append_hc_dictionary(&mut ctx.dictionary, &dst[..n]);
3595            n as c_int
3596        }
3597        None => -1,
3598    }
3599}
3600
3601/// Clamp the acceleration parameter to upstream's accepted range.
3602fn normalize_acceleration(acceleration: c_int) -> usize {
3603    acceleration.clamp(1, 65_537) as usize
3604}
3605
3606trait FastHashTable {
3607    fn get(&self, h: usize) -> usize;
3608    fn set(&mut self, h: usize, pos: usize);
3609}
3610
3611impl FastHashTable for [u32] {
3612    #[inline]
3613    fn get(&self, h: usize) -> usize {
3614        self[h] as usize
3615    }
3616
3617    #[inline]
3618    fn set(&mut self, h: usize, pos: usize) {
3619        self[h] = pos as u32;
3620    }
3621}
3622
3623impl FastHashTable for [u16] {
3624    #[inline]
3625    fn get(&self, h: usize) -> usize {
3626        self[h] as usize
3627    }
3628
3629    #[inline]
3630    fn set(&mut self, h: usize, pos: usize) {
3631        self[h] = pos as u16;
3632    }
3633}
3634
3635/// Single-pass fast LZ4 block compressor without a dictionary. Mirrors upstream
3636/// `LZ4_compress_generic_validated()` (lz4.c:926+) for the
3637/// `outputDirective == notLimited`, `dictDirective == noDict` configuration:
3638/// branches are decided at compile time and the function presumes valid input.
3639/// Returns the number of compressed bytes written, or `None` if encoding could
3640/// not fit in `dst`.
3641fn compress_block(src: &[u8], dst: &mut [u8], acceleration: usize) -> Option<usize> {
3642    if src.len() < LZ4_64K_LIMIT {
3643        let mut table = vec![0u16; 1 << LZ4_HASH_BITS_U16];
3644        compress_block_with_table::<true, _>(src, dst, acceleration, &mut table[..])
3645    } else {
3646        let mut table = vec![0u32; 1 << LZ4_HASH_BITS];
3647        compress_block_with_table::<false, _>(src, dst, acceleration, &mut table[..])
3648    }
3649}
3650
3651fn compress_block_ext_state(
3652    state: *mut c_void,
3653    src: &[u8],
3654    dst: &mut [u8],
3655    acceleration: usize,
3656) -> Option<usize> {
3657    if src.len() < LZ4_64K_LIMIT {
3658        let table = unsafe { slice::from_raw_parts_mut(state as *mut u16, 1 << LZ4_HASH_BITS_U16) };
3659        table.fill(0);
3660        compress_block_with_table::<true, _>(src, dst, acceleration, table)
3661    } else {
3662        let table = unsafe { slice::from_raw_parts_mut(state as *mut u32, 1 << LZ4_HASH_BITS) };
3663        table.fill(0);
3664        compress_block_with_table::<false, _>(src, dst, acceleration, table)
3665    }
3666}
3667
3668fn compress_block_with_table<const BY_U16: bool, T: FastHashTable + ?Sized>(
3669    src: &[u8],
3670    dst: &mut [u8],
3671    acceleration: usize,
3672    table: &mut T,
3673) -> Option<usize> {
3674    if src.is_empty() {
3675        return emit_last_literals(src, dst, 0, 0);
3676    }
3677    if src.len() < MFLIMIT + 1 {
3678        return emit_last_literals(src, dst, 0, 0);
3679    }
3680    let mut ip = 0usize;
3681    let mut anchor = 0usize;
3682    let mut op = 0usize;
3683    let mflimit_plus_one = src.len() - MFLIMIT + 1;
3684    let match_limit = src.len() - LAST_LITERALS;
3685
3686    table.set(hash_fast_const::<BY_U16>(src, ip), ip);
3687    ip += 1;
3688    let mut forward_h = hash_fast_const::<BY_U16>(src, ip);
3689
3690    let src_ptr = src.as_ptr();
3691
3692    loop {
3693        let mut forward_ip = ip;
3694        let mut step = 1usize;
3695        let mut search_match_nb = acceleration << 6;
3696        let mut ref_pos;
3697
3698        loop {
3699            let h = forward_h;
3700            ip = forward_ip;
3701            forward_ip += step;
3702            step = search_match_nb >> 6;
3703            search_match_nb += 1;
3704
3705            if forward_ip > mflimit_plus_one {
3706                return emit_last_literals(src, dst, anchor, op);
3707            }
3708
3709            ref_pos = table.get(h);
3710            forward_h = hash_fast_const::<BY_U16>(src, forward_ip);
3711            table.set(h, ip);
3712
3713            // Mirrors upstream's `LZ4_read32(match) == LZ4_read32(ip)` —
3714            // a single 4-byte unaligned compare instead of a slice memcmp.
3715            if ip > ref_pos
3716                && ip - ref_pos <= LZ4_DISTANCE_MAX
3717                && unsafe { read_u32_ptr(src_ptr.add(ref_pos)) == read_u32_ptr(src_ptr.add(ip)) }
3718            {
3719                break;
3720            }
3721        }
3722
3723        while ip > anchor && ref_pos > 0 && src[ip - 1] == src[ref_pos - 1] {
3724            ip -= 1;
3725            ref_pos -= 1;
3726        }
3727
3728        loop {
3729            let match_len =
3730                MINMATCH + count_match(src, ip + MINMATCH, ref_pos + MINMATCH, match_limit);
3731            op = encode_sequence(src, dst, anchor, ip, match_len, ip - ref_pos, op)?;
3732            ip += match_len;
3733            anchor = ip;
3734
3735            if ip >= mflimit_plus_one {
3736                return emit_last_literals(src, dst, anchor, op);
3737            }
3738
3739            table.set(hash_fast_const::<BY_U16>(src, ip - 2), ip - 2);
3740            let h = hash_fast_const::<BY_U16>(src, ip);
3741            ref_pos = table.get(h);
3742            table.set(h, ip);
3743            if ip > ref_pos
3744                && ip - ref_pos <= LZ4_DISTANCE_MAX
3745                && unsafe { read_u32_ptr(src_ptr.add(ref_pos)) == read_u32_ptr(src_ptr.add(ip)) }
3746            {
3747                continue;
3748            }
3749
3750            ip += 1;
3751            forward_h = hash_fast_const::<BY_U16>(src, ip);
3752            break;
3753        }
3754    }
3755}
3756
3757/// Fast LZ4 block compressor under the `fillOutput` directive: writes as much
3758/// of `src` as can fit into `dst` and returns `(consumed, written)`. Mirrors
3759/// upstream `LZ4_compress_generic_validated(..., fillOutput, ...)` in lz4.c.
3760/// In contrast to `compress_block`, encoding never simply bails on overflow:
3761/// when the destination cannot hold the next sequence, the parser stops or
3762/// truncates the trailing literals run.
3763fn compress_dest_size(src: &[u8], dst: &mut [u8], acceleration: usize) -> Option<(usize, usize)> {
3764    // Single-pass fillOutput compressor, mirroring upstream
3765    // `LZ4_compress_generic(..., fillOutput, ...)` (lz4.c:931+). The
3766    // previous implementation binary-searched over input prefixes; this
3767    // version makes one pass and truncates when the destination fills.
3768    if dst.is_empty() {
3769        return None;
3770    }
3771    let olimit = dst.len();
3772
3773    if src.len() < MFLIMIT + 1 {
3774        return Some(emit_truncated_last_literals(src, dst, 0, 0, olimit));
3775    }
3776
3777    if src.len() < LZ4_64K_LIMIT {
3778        compress_dest_size_inner::<true>(src, dst, acceleration, olimit)
3779    } else {
3780        compress_dest_size_inner::<false>(src, dst, acceleration, olimit)
3781    }
3782}
3783
3784fn compress_dest_size_inner<const BY_U16: bool>(
3785    src: &[u8],
3786    dst: &mut [u8],
3787    acceleration: usize,
3788    olimit: usize,
3789) -> Option<(usize, usize)> {
3790    // Tail spaces required by upstream's pre-checks. The "+ MFLIMIT - MINMATCH"
3791    // term reserves room so the parser can always fall through to a final
3792    // literal run that satisfies LZ4's end-of-block constraint.
3793    let tail = MFLIMIT - MINMATCH; // = 8
3794    let last_lit = LAST_LITERALS; // = 5
3795    let hash_bits = if BY_U16 {
3796        LZ4_HASH_BITS_U16
3797    } else {
3798        LZ4_HASH_BITS
3799    };
3800    // u32 hash table entries — see compress_block above for rationale.
3801    let mut table = vec![0u32; 1 << hash_bits];
3802    let mut ip = 0usize;
3803    let mut anchor = 0usize;
3804    let mut op = 0usize;
3805    let mflimit_plus_one = src.len() - MFLIMIT + 1;
3806    let match_limit = src.len() - LAST_LITERALS;
3807
3808    table[hash_fast_const::<BY_U16>(src, ip)] = ip as u32;
3809    ip += 1;
3810    let mut forward_h = hash_fast_const::<BY_U16>(src, ip);
3811
3812    'outer: loop {
3813        let mut forward_ip = ip;
3814        let mut step = 1usize;
3815        let mut search_match_nb = acceleration << 6;
3816        let mut ref_pos;
3817
3818        loop {
3819            let h = forward_h;
3820            ip = forward_ip;
3821            forward_ip += step;
3822            step = search_match_nb >> 6;
3823            search_match_nb += 1;
3824
3825            if forward_ip > mflimit_plus_one {
3826                break 'outer;
3827            }
3828
3829            ref_pos = table[h] as usize;
3830            forward_h = hash_fast_const::<BY_U16>(src, forward_ip);
3831            table[h] = ip as u32;
3832
3833            if ip > ref_pos
3834                && ip - ref_pos <= LZ4_DISTANCE_MAX
3835                && unsafe {
3836                    read_u32_ptr(src.as_ptr().add(ref_pos)) == read_u32_ptr(src.as_ptr().add(ip))
3837                }
3838            {
3839                break;
3840            }
3841        }
3842
3843        while ip > anchor && ref_pos > 0 && src[ip - 1] == src[ref_pos - 1] {
3844            ip -= 1;
3845            ref_pos -= 1;
3846        }
3847
3848        loop {
3849            let lit_len = ip - anchor;
3850
3851            // Upstream's pre-token fillOutput check (lz4.c:1114-1118):
3852            //   op + (litLength+240)/255 + litLength + 2 + 1 + MFLIMIT - MINMATCH > olimit
3853            if op + (lit_len + 240) / 255 + lit_len + 2 + 1 + tail > olimit {
3854                break 'outer;
3855            }
3856
3857            let token_pos = op;
3858            op += 1;
3859            if lit_len >= 15 {
3860                let mut extra = lit_len - 15;
3861                while extra >= 255 {
3862                    dst[op] = 255;
3863                    op += 1;
3864                    extra -= 255;
3865                }
3866                dst[op] = extra as u8;
3867                op += 1;
3868            }
3869            dst[op..op + lit_len].copy_from_slice(&src[anchor..ip]);
3870            op += lit_len;
3871
3872            // Upstream's pre-offset fillOutput check (lz4.c:1143-1148): if the
3873            // match is too close to the end, rewind the token and bail.
3874            if op + 2 + 1 + tail > olimit {
3875                op = token_pos;
3876                break 'outer;
3877            }
3878
3879            let offset = ip - ref_pos;
3880            dst[op..op + 2].copy_from_slice(&(offset as u16).to_le_bytes());
3881            op += 2;
3882
3883            let mut match_code = count_match(src, ip + MINMATCH, ref_pos + MINMATCH, match_limit);
3884            ip += match_code + MINMATCH;
3885
3886            // Upstream's match-code overflow path (lz4.c:1183-1208): in
3887            // fillOutput mode, shrink the match instead of bailing. The
3888            // formula computes the largest match_code whose length-extension
3889            // bytes still fit alongside the reserved last-literals room.
3890            if op + (1 + last_lit) + (match_code + 240) / 255 > olimit {
3891                let avail = olimit.saturating_sub(op + 1 + last_lit);
3892                let new_match_code = 14 + avail.saturating_mul(255);
3893                if new_match_code < match_code {
3894                    ip -= match_code - new_match_code;
3895                    match_code = new_match_code;
3896                } else {
3897                    // Shouldn't happen given the trigger condition, but stay
3898                    // defensive — leave match_code untouched.
3899                }
3900            }
3901
3902            let token_lit_bits = (cmp::min(lit_len, 15) as u8) << 4;
3903            if match_code >= 15 {
3904                dst[token_pos] = token_lit_bits | 0x0F;
3905                let mut ml = match_code - 15;
3906                while ml >= 255 {
3907                    dst[op] = 255;
3908                    op += 1;
3909                    ml -= 255;
3910                }
3911                dst[op] = ml as u8;
3912                op += 1;
3913            } else {
3914                dst[token_pos] = token_lit_bits | (match_code as u8);
3915            }
3916
3917            anchor = ip;
3918
3919            if ip >= mflimit_plus_one {
3920                break 'outer;
3921            }
3922
3923            table[hash_fast_const::<BY_U16>(src, ip - 2)] = (ip - 2) as u32;
3924            let h = hash_fast_const::<BY_U16>(src, ip);
3925            let next_ref = table[h] as usize;
3926            table[h] = ip as u32;
3927            if ip > next_ref
3928                && ip - next_ref <= LZ4_DISTANCE_MAX
3929                && unsafe {
3930                    read_u32_ptr(src.as_ptr().add(next_ref)) == read_u32_ptr(src.as_ptr().add(ip))
3931                }
3932            {
3933                ref_pos = next_ref;
3934                continue;
3935            }
3936
3937            ip += 1;
3938            forward_h = hash_fast_const::<BY_U16>(src, ip);
3939            break;
3940        }
3941    }
3942
3943    Some(emit_truncated_last_literals(src, dst, anchor, op, olimit))
3944}
3945
3946/// Emit a final-literals run for the `fillOutput` path, truncating the run if
3947/// it would overflow the output buffer. Mirrors upstream's `_last_literals`
3948/// fallback block (lz4.c:1298-1325) under the `fillOutput` directive. Returns
3949/// `(consumed, written)`.
3950fn emit_truncated_last_literals(
3951    src: &[u8],
3952    dst: &mut [u8],
3953    anchor: usize,
3954    mut op: usize,
3955    olimit: usize,
3956) -> (usize, usize) {
3957    // Mirrors upstream's `_last_literals` block (lz4.c:1298-1325) under the
3958    // fillOutput directive. Returns (consumed, written).
3959    let mut last_run = src.len() - anchor;
3960    let needed = op + last_run + 1 + (last_run + 255 - 15) / 255;
3961    if needed > olimit {
3962        // Upstream:
3963        //   lastRun  = (size_t)(olimit-op) - 1;
3964        //   lastRun -= (lastRun + 256 - RUN_MASK) / 256;
3965        let avail = olimit.saturating_sub(op + 1);
3966        let extension_bytes = (avail + 256 - 15) / 256;
3967        last_run = avail.saturating_sub(extension_bytes);
3968    }
3969    if op >= olimit {
3970        // No room even for a token — return what we have.
3971        return (anchor, op);
3972    }
3973    if last_run >= 15 {
3974        dst[op] = 0xF0; // RUN_MASK << ML_BITS
3975        op += 1;
3976        let mut accum = last_run - 15;
3977        while accum >= 255 {
3978            dst[op] = 255;
3979            op += 1;
3980            accum -= 255;
3981        }
3982        dst[op] = accum as u8;
3983        op += 1;
3984    } else {
3985        dst[op] = (last_run as u8) << 4;
3986        op += 1;
3987    }
3988    dst[op..op + last_run].copy_from_slice(&src[anchor..anchor + last_run]);
3989    op += last_run;
3990    (anchor + last_run, op)
3991}
3992
3993/// Fast LZ4 block compressor with a preceding dictionary. Concatenates the
3994/// (truncated to `LZ4_DISTANCE_MAX`) dictionary in front of `src` and runs the
3995/// same parser as `compress_block`, mirroring upstream
3996/// `LZ4_compress_generic_validated()` for the `dictDirective == withPrefix64k`
3997/// configuration. The dictionary tail is seeded into the hash table before the
3998/// main loop so backward matches can land inside it.
3999fn compress_block_with_dict(
4000    src: &[u8],
4001    dst: &mut [u8],
4002    dict: &[u8],
4003    acceleration: usize,
4004) -> Option<usize> {
4005    if src.is_empty() {
4006        return emit_last_literals(src, dst, 0, 0);
4007    }
4008    if src.len() < MFLIMIT + 1 {
4009        return emit_last_literals(src, dst, 0, 0);
4010    }
4011    let dict_keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
4012    let dict = &dict[dict.len() - dict_keep..];
4013    if dict.is_empty() {
4014        return compress_block(src, dst, acceleration);
4015    }
4016
4017    let mut full = Vec::with_capacity(dict.len() + src.len());
4018    full.extend_from_slice(dict);
4019    full.extend_from_slice(src);
4020    let base = dict.len();
4021    // u32 hash table entries — see compress_block above for rationale.
4022    let mut table = vec![0u32; 1 << LZ4_HASH_BITS];
4023    let seed_end = full.len().saturating_sub(MINMATCH - 1);
4024    for pos in 0..cmp::min(base, seed_end) {
4025        table[hash_fast(&full, pos, false)] = pos as u32;
4026    }
4027
4028    let mut ip = base;
4029    let mut anchor = base;
4030    let mut op = 0usize;
4031    let mflimit_plus_one = base + src.len() - MFLIMIT + 1;
4032    let match_limit = base + src.len() - LAST_LITERALS;
4033
4034    table[hash_fast(&full, ip, false)] = ip as u32;
4035    ip += 1;
4036    let mut forward_h = hash_fast(&full, ip, false);
4037
4038    let full_ptr = full.as_ptr();
4039
4040    loop {
4041        let mut forward_ip = ip;
4042        let mut step = 1usize;
4043        let mut search_match_nb = acceleration << 6;
4044        let mut ref_pos;
4045
4046        loop {
4047            let h = forward_h;
4048            ip = forward_ip;
4049            forward_ip += step;
4050            step = search_match_nb >> 6;
4051            search_match_nb += 1;
4052
4053            if forward_ip > mflimit_plus_one {
4054                return emit_last_literals_with_base(&full, dst, base, anchor, op);
4055            }
4056
4057            ref_pos = table[h] as usize;
4058            forward_h = hash_fast(&full, forward_ip, false);
4059            table[h] = ip as u32;
4060
4061            if ip > ref_pos
4062                && ip - ref_pos <= LZ4_DISTANCE_MAX
4063                && unsafe { read_u32_ptr(full_ptr.add(ref_pos)) == read_u32_ptr(full_ptr.add(ip)) }
4064            {
4065                break;
4066            }
4067        }
4068
4069        while ip > anchor && ref_pos > 0 && full[ip - 1] == full[ref_pos - 1] {
4070            ip -= 1;
4071            ref_pos -= 1;
4072        }
4073
4074        loop {
4075            let match_len =
4076                MINMATCH + count_match(&full, ip + MINMATCH, ref_pos + MINMATCH, match_limit);
4077            op = encode_sequence(&full, dst, anchor, ip, match_len, ip - ref_pos, op)?;
4078            ip += match_len;
4079            anchor = ip;
4080
4081            if ip >= mflimit_plus_one {
4082                return emit_last_literals_with_base(&full, dst, base, anchor, op);
4083            }
4084
4085            table[hash_fast(&full, ip - 2, false)] = (ip - 2) as u32;
4086            let h = hash_fast(&full, ip, false);
4087            ref_pos = table[h] as usize;
4088            table[h] = ip as u32;
4089            if ip > ref_pos
4090                && ip - ref_pos <= LZ4_DISTANCE_MAX
4091                && unsafe { read_u32_ptr(full_ptr.add(ref_pos)) == read_u32_ptr(full_ptr.add(ip)) }
4092            {
4093                continue;
4094            }
4095
4096            ip += 1;
4097            forward_h = hash_fast(&full, ip, false);
4098            break;
4099        }
4100    }
4101}
4102
4103#[derive(Clone, Copy, Debug)]
4104struct HcMatch {
4105    start: usize,
4106    len: usize,
4107    off: usize,
4108}
4109
4110#[derive(Clone, Copy, Debug)]
4111#[repr(C)]
4112struct HcOptimal {
4113    // u32 instead of usize: `price`, `off`, `mlen`, `litlen` all stay well
4114    // under 2^32 (the maximum block size is `LZ4_OPT_NUM = 4096`, distances
4115    // are bounded by `LZ4_DISTANCE_MAX = 65535`, and prices are bounded by
4116    // a small multiple of `LZ4_OPT_NUM`). Halving the struct from 32 bytes
4117    // to 16 bytes cuts the `opt` table from 128 KiB to 64 KiB and is a
4118    // meaningful cache-traffic reduction in the inner optimal-parser loop.
4119    //
4120    // NOTE: Tried shrinking off/mlen/litlen to u16 (12-byte struct) — that
4121    // actually slowed HC10/11/12 by 30-40% because the awkward 12-byte
4122    // entry layout (5 per 64-byte cache line with 4 bytes of slack)
4123    // produces worse codegen and unaligned accesses. 16 bytes (4 entries
4124    // per cache line, all u32-aligned) is the sweet spot.
4125    price: u32,
4126    off: u32,
4127    mlen: u32,
4128    litlen: u32,
4129}
4130
4131/// Starting offset added to every physical position before it is stored in
4132/// `HcTables::hash` or used for chain arithmetic. Mirrors upstream's
4133/// `LZ4HC_init_internal` which pushes `dictLimit` and `lowLimit` to at least
4134/// `64 KiB` so the `u16`-delta chain walk can never wrap a valid candidate
4135/// below zero: any logical position stored in the hash table is `>= 64 KiB`
4136/// and every chain delta is `<= 65535`, so `candidate_log -= delta` stays
4137/// positive and the `candidate_log >= lowest_log` exit is exact.
4138const HC_OFFSET: u32 = (LZ4_DISTANCE_MAX + 1) as u32;
4139
4140/// Size of the chain table — fixed by the LZ4 format. Hoisted to a const
4141/// so the array types in `HcTables` can use it directly.
4142const HC_CHAIN_SIZE: usize = LZ4_DISTANCE_MAX + 1;
4143
4144#[derive(Debug)]
4145struct HcTables {
4146    /// Head-of-chain per hash: logical positions (`physical + HC_OFFSET`).
4147    /// Initialized to `0` so an empty bucket yields a `candidate_log < HC_OFFSET`
4148    /// that fails the `candidate_log >= lowest_log` loop condition without any
4149    /// explicit sentinel check.
4150    ///
4151    /// Stored as `Box<[u32; LZ4HC_HASH_SIZE]>` rather than `Vec<u32>` so the
4152    /// length is compile-time known and LLVM can DCE per-access bounds
4153    /// checks in `insert_until`'s hot loop.
4154    hash: Box<[u32; LZ4HC_HASH_SIZE]>,
4155    /// Delta to the previous chain entry, clamped to `LZ4_DISTANCE_MAX`.
4156    /// Initial `0xFFFF` entries mirror upstream's `MEM_INIT(chainTable, 0xFF)`
4157    /// so first-ever walks step off the end of the search window immediately.
4158    /// Same `Box<[_; N]>` trick as `hash` above.
4159    chain: Box<[u16; HC_CHAIN_SIZE]>,
4160    next_to_update: usize,
4161    /// Offset inside the search buffer where the current prefix begins.
4162    /// For no-dict compression this is `0`; when a dictionary is prepended
4163    /// before the input, it is `dict.len()`. Equivalent to upstream's
4164    /// `prefixPtr` in that `prefixPtr == &full[base]`. Used by the HC
4165    /// pattern-analysis branch to replicate upstream's `protectDictEnd`
4166    /// guard so the repeated-pattern fast path is not entered when a
4167    /// dict-area candidate sits in the last 3 bytes before the prefix.
4168    base: usize,
4169}
4170
4171impl HcTables {
4172    fn with_base(_src_len: usize, base: usize) -> Self {
4173        Self {
4174            hash: vec![0u32; LZ4HC_HASH_SIZE]
4175                .into_boxed_slice()
4176                .try_into()
4177                .unwrap(),
4178            chain: vec![LZ4_DISTANCE_MAX as u16; HC_CHAIN_SIZE]
4179                .into_boxed_slice()
4180                .try_into()
4181                .unwrap(),
4182            next_to_update: 0,
4183            base,
4184        }
4185    }
4186
4187    /// Convert a physical buffer position to the logical position used for
4188    /// storage in `hash` and for chain arithmetic.
4189    #[inline(always)]
4190    fn to_log(pos: usize) -> u32 {
4191        (pos as u32).wrapping_add(HC_OFFSET)
4192    }
4193
4194    /// Convert a logical position back to the physical buffer position. Only
4195    /// valid after a `>= lowest_log` check has ensured the logical position
4196    /// is at least `HC_OFFSET`.
4197    #[inline(always)]
4198    fn to_phys(log: u32) -> usize {
4199        log.wrapping_sub(HC_OFFSET) as usize
4200    }
4201
4202    #[inline]
4203    fn insert_until(&mut self, src: &[u8], target: usize) {
4204        let end = cmp::min(target, src.len().saturating_sub(MINMATCH - 1));
4205        while self.next_to_update < end {
4206            let pos = self.next_to_update;
4207            let h = hash4_hc(src, pos);
4208            let log_pos = Self::to_log(pos);
4209            let prev_log = self.hash[h];
4210            // delta = log_pos - prev_log, clamped to LZ4_DISTANCE_MAX. When
4211            // `prev_log` is 0 (empty bucket) the computed delta always exceeds
4212            // 65535, so it clamps to the sentinel and a later chain walk
4213            // lands below `lowest_log` on the next step.
4214            let delta = cmp::min(log_pos - prev_log, HC_OFFSET - 1) as u16;
4215            self.chain[pos & LZ4_DISTANCE_MAX] = delta;
4216            self.hash[h] = log_pos;
4217            self.next_to_update += 1;
4218        }
4219    }
4220
4221    /// Returns the physical position stored in the chain before `pos`, or
4222    /// `usize::MAX` if the chain step goes below the HC search window (the
4223    /// logical delta lands at or below `HC_OFFSET`).
4224    #[inline]
4225    fn previous(&self, pos: usize) -> usize {
4226        let log_pos = Self::to_log(pos);
4227        let delta = self.chain[pos & LZ4_DISTANCE_MAX] as u32;
4228        let prev_log = log_pos.wrapping_sub(delta);
4229        if prev_log < HC_OFFSET {
4230            usize::MAX
4231        } else {
4232            Self::to_phys(prev_log)
4233        }
4234    }
4235}
4236
4237struct MidTables {
4238    hash4: Vec<usize>,
4239    hash8: Vec<usize>,
4240}
4241
4242impl MidTables {
4243    fn new() -> Self {
4244        Self {
4245            hash4: vec![usize::MAX; LZ4MID_HASH_SIZE],
4246            hash8: vec![usize::MAX; LZ4MID_HASH_SIZE],
4247        }
4248    }
4249
4250    fn add4(&mut self, src: &[u8], pos: usize) {
4251        self.add4_index(src, pos, pos);
4252    }
4253
4254    fn add4_index(&mut self, src: &[u8], pos: usize, index: usize) {
4255        if pos + MINMATCH <= src.len() {
4256            self.hash4[hash4_mid(src, pos)] = index;
4257        }
4258    }
4259
4260    fn add8(&mut self, src: &[u8], pos: usize) {
4261        self.add8_index(src, pos, pos);
4262    }
4263
4264    fn add8_index(&mut self, src: &[u8], pos: usize, index: usize) {
4265        if pos + LZ4MID_HASHSIZE <= src.len() {
4266            self.hash8[hash8_mid(src, pos)] = index;
4267        }
4268    }
4269}
4270
4271/// Fill the LZ4mid hash tables with references into the dictionary prefix.
4272/// Mirrors upstream `LZ4MID_fillHTable()` (lz4hc.c:514): "the resulting table
4273/// is only exploitable by LZ4MID (level 2)". The first pass strides by 3 and
4274/// updates both `hash4` and `hash8`; the second pass dense-updates `hash8`
4275/// over the most recent 32 KiB so short-distance matches near the prefix
4276/// boundary still hit.
4277fn fill_lz4mid_dict_table(table: &mut MidTables, full: &[u8], base: usize) {
4278    if base <= LZ4MID_HASHSIZE {
4279        return;
4280    }
4281    let target = base - LZ4MID_HASHSIZE;
4282    let mut idx = 0usize;
4283    while idx < target {
4284        table.add4_index(full, idx, idx);
4285        table.add8_index(full, idx + 1, idx + 1);
4286        idx += 3;
4287    }
4288
4289    idx = if base > 32 * 1024 + LZ4MID_HASHSIZE {
4290        target - 32 * 1024
4291    } else {
4292        0
4293    };
4294    while idx < target {
4295        table.add8_index(full, idx, idx);
4296        idx += 1;
4297    }
4298}
4299
4300/// LZ4mid (level 2) compressor without a dictionary. Trampolines into
4301/// `compress_block_lz4mid_with_base` with `base == 0`.
4302fn compress_block_lz4mid(src: &[u8], dst: &mut [u8]) -> Option<usize> {
4303    compress_block_lz4mid_with_base(src, dst, 0)
4304}
4305
4306/// LZ4mid (level 2) block compressor. Mirrors upstream `LZ4MID_compress()`
4307/// (lz4hc.c:554+): a two-table (hash4 + hash8) parser that probes the longer
4308/// hash first, optionally peeks one position ahead, and adopts the better
4309/// match. `base` is the offset inside `full` where the input to be emitted
4310/// starts; `full[..base]` is retained dictionary prefix (or empty).
4311fn compress_block_lz4mid_with_base(full: &[u8], dst: &mut [u8], base: usize) -> Option<usize> {
4312    let src_len = full.len().checked_sub(base)?;
4313    if src_len == 0 {
4314        return emit_last_literals_with_base(full, dst, base, base, 0);
4315    }
4316    if src_len < MFLIMIT + 1 {
4317        return emit_last_literals_with_base(full, dst, base, base, 0);
4318    }
4319
4320    let mut table = MidTables::new();
4321    if base > 0 {
4322        fill_lz4mid_dict_table(&mut table, full, base);
4323    }
4324    let mut anchor = base;
4325    let mut ip = base;
4326    let mut op = 0usize;
4327    let mflimit = full.len() - MFLIMIT;
4328    let match_limit = full.len() - LAST_LITERALS;
4329
4330    while ip <= mflimit {
4331        let search_ip = ip;
4332        let mut match_len = 0usize;
4333        let mut match_distance = 0usize;
4334
4335        let h8 = hash8_mid(full, ip);
4336        let pos8 = table.hash8[h8];
4337        table.hash8[h8] = ip;
4338        if pos8 != usize::MAX
4339            && ip - pos8 <= LZ4_DISTANCE_MAX
4340            && unsafe {
4341                read_u32_ptr(full.as_ptr().add(pos8)) == read_u32_ptr(full.as_ptr().add(ip))
4342            }
4343        {
4344            match_len = count_match(full, ip, pos8, match_limit);
4345            if match_len >= MINMATCH {
4346                match_distance = ip - pos8;
4347            }
4348        }
4349
4350        if match_len < MINMATCH {
4351            let h4 = hash4_mid(full, ip);
4352            let pos4 = table.hash4[h4];
4353            table.hash4[h4] = ip;
4354            if pos4 != usize::MAX
4355                && ip - pos4 <= LZ4_DISTANCE_MAX
4356                && unsafe {
4357                    read_u32_ptr(full.as_ptr().add(pos4)) == read_u32_ptr(full.as_ptr().add(ip))
4358                }
4359            {
4360                match_len = count_match(full, ip, pos4, match_limit);
4361                if match_len >= MINMATCH {
4362                    match_distance = ip - pos4;
4363
4364                    let ip1 = ip + 1;
4365                    if ip < mflimit {
4366                        let h8_next = hash8_mid(full, ip1);
4367                        let pos8_next = table.hash8[h8_next];
4368                        let dist2 = ip1.saturating_sub(pos8_next);
4369                        if pos8_next != usize::MAX
4370                            && dist2 <= LZ4_DISTANCE_MAX
4371                            && unsafe {
4372                                read_u32_ptr(full.as_ptr().add(pos8_next))
4373                                    == read_u32_ptr(full.as_ptr().add(ip1))
4374                            }
4375                        {
4376                            let len2 = count_match(full, ip1, pos8_next, match_limit);
4377                            if len2 > match_len {
4378                                table.hash8[h8_next] = ip1;
4379                                ip = ip1;
4380                                match_len = len2;
4381                                match_distance = dist2;
4382                            }
4383                        }
4384                    }
4385                }
4386            }
4387        }
4388
4389        if match_len < MINMATCH {
4390            ip += 1 + ((ip - anchor) >> 9);
4391            continue;
4392        }
4393
4394        while ip > anchor && ip > match_distance && full[ip - 1] == full[ip - match_distance - 1] {
4395            ip -= 1;
4396            match_len += 1;
4397        }
4398
4399        table.add8_index(full, ip + 1, search_ip + 1);
4400        table.add8_index(full, ip + 2, search_ip + 2);
4401        table.add4_index(full, ip + 1, search_ip + 1);
4402
4403        op = encode_sequence(full, dst, anchor, ip, match_len, match_distance, op)?;
4404        ip += match_len;
4405        anchor = ip;
4406
4407        if ip >= 2 && ip - 2 < full.len().saturating_sub(LZ4MID_HASHSIZE) {
4408            if ip >= 5 {
4409                table.add8(full, ip - 5);
4410            }
4411            if ip >= 3 {
4412                table.add8(full, ip - 3);
4413            }
4414            table.add8(full, ip - 2);
4415            table.add4(full, ip - 2);
4416            table.add4(full, ip - 1);
4417        }
4418    }
4419
4420    emit_last_literals_with_base(full, dst, base, anchor, op)
4421}
4422
4423/// HC block compressor entry point without a dictionary. Mirrors upstream
4424/// `LZ4HC_compress_generic_internal()` (lz4hc.c:1419+): dispatches to the
4425/// LZ4mid strategy at levels 1-2, the optimal parser at levels 10-12, and the
4426/// hash-chain parser otherwise. `compression_level` is normalized via
4427/// `normalize_hc_level` so out-of-range values map to upstream defaults.
4428fn compress_block_hc(
4429    src: &[u8],
4430    dst: &mut [u8],
4431    compression_level: c_int,
4432    favor_dec_speed: bool,
4433) -> Option<usize> {
4434    if src.is_empty() || src.len() < MFLIMIT + 1 {
4435        return emit_last_literals(src, dst, 0, 0);
4436    }
4437    let level = normalize_hc_level(compression_level);
4438    if level <= 2 {
4439        return compress_block_lz4mid(src, dst);
4440    }
4441    if level >= 10 {
4442        return compress_block_hc_optimal(src, dst, 0, level, favor_dec_speed);
4443    }
4444    compress_block_hc_hashchain(src, dst, 0, level)
4445}
4446
4447/// Upstream `LZ4HC_compress_hashChain()` port, shared between no-dict and
4448/// dict compression. `base` is the offset inside `full` where the input to be
4449/// emitted starts; `full[..base]` is retained history (either empty or the
4450/// concatenated dictionary).
4451fn compress_block_hc_hashchain(
4452    full: &[u8],
4453    dst: &mut [u8],
4454    base: usize,
4455    level: c_int,
4456) -> Option<usize> {
4457    match level {
4458        3 => compress_block_hc_hashchain_impl::<4, false>(full, dst, base),
4459        4 => compress_block_hc_hashchain_impl::<8, false>(full, dst, base),
4460        5 => compress_block_hc_hashchain_impl::<16, false>(full, dst, base),
4461        6 => compress_block_hc_hashchain_impl::<32, false>(full, dst, base),
4462        7 => compress_block_hc_hashchain_impl::<64, false>(full, dst, base),
4463        8 => compress_block_hc_hashchain_impl::<128, false>(full, dst, base),
4464        _ => compress_block_hc_hashchain_impl::<256, true>(full, dst, base),
4465    }
4466}
4467
4468fn compress_block_hc_hashchain_impl<const ATTEMPTS: usize, const PATTERN_ANALYSIS: bool>(
4469    full: &[u8],
4470    dst: &mut [u8],
4471    base: usize,
4472) -> Option<usize> {
4473    let mut table = HcTables::with_base(full.len(), base);
4474    if base > 0 {
4475        table.insert_until(full, base);
4476    }
4477
4478    let mut anchor = base;
4479    let mut op = 0usize;
4480    let mflimit = full.len() - MFLIMIT;
4481    let match_limit = full.len() - LAST_LITERALS;
4482    let nomatch = HcMatch {
4483        start: 0,
4484        len: 0,
4485        off: 0,
4486    };
4487    let mut ip = base;
4488
4489    while ip <= mflimit {
4490        let mut m1 = find_hc_match::<PATTERN_ANALYSIS>(full, &mut table, ip, match_limit, ATTEMPTS);
4491        if m1.len < MINMATCH {
4492            ip += 1;
4493            continue;
4494        }
4495        let mut start0 = ip;
4496        let mut m0 = m1;
4497
4498        'search2: loop {
4499            let mut start2;
4500            let mut m2;
4501            if ip + m1.len <= mflimit {
4502                start2 = ip + m1.len - 2;
4503                m2 = find_hc_wider_match::<PATTERN_ANALYSIS, false, false>(
4504                    full,
4505                    &mut table,
4506                    start2,
4507                    ip,
4508                    match_limit,
4509                    m1.len,
4510                    ATTEMPTS,
4511                );
4512                start2 = m2.start;
4513            } else {
4514                m2 = nomatch;
4515                start2 = 0;
4516            }
4517
4518            if m2.len <= m1.len {
4519                op = encode_sequence(full, dst, anchor, ip, m1.len, m1.off, op)?;
4520                ip += m1.len;
4521                anchor = ip;
4522                break 'search2;
4523            }
4524
4525            if start0 < ip && start2 < ip + m0.len {
4526                ip = start0;
4527                m1 = m0;
4528            }
4529
4530            if start2 - ip < 3 {
4531                ip = start2;
4532                m1 = m2;
4533                continue 'search2;
4534            }
4535
4536            'search3: loop {
4537                if start2 - ip < OPTIMAL_ML {
4538                    let mut new_len = cmp::min(m1.len, OPTIMAL_ML);
4539                    if ip + new_len > start2 + m2.len - MINMATCH {
4540                        new_len = start2 - ip + m2.len - MINMATCH;
4541                    }
4542                    let correction = new_len.saturating_sub(start2 - ip);
4543                    if correction > 0 {
4544                        start2 += correction;
4545                        m2.start = start2;
4546                        m2.len -= correction;
4547                    }
4548                }
4549
4550                let mut start3;
4551                let m3;
4552                if start2 + m2.len <= mflimit {
4553                    start3 = start2 + m2.len - 3;
4554                    m3 = find_hc_wider_match::<PATTERN_ANALYSIS, false, false>(
4555                        full,
4556                        &mut table,
4557                        start3,
4558                        start2,
4559                        match_limit,
4560                        m2.len,
4561                        ATTEMPTS,
4562                    );
4563                    start3 = m3.start;
4564                } else {
4565                    m3 = nomatch;
4566                    start3 = 0;
4567                }
4568
4569                if m3.len <= m2.len {
4570                    if start2 < ip + m1.len {
4571                        m1.len = start2 - ip;
4572                    }
4573                    op = encode_sequence(full, dst, anchor, ip, m1.len, m1.off, op)?;
4574                    ip += m1.len;
4575                    anchor = ip;
4576
4577                    ip = start2;
4578                    op = encode_sequence(full, dst, anchor, ip, m2.len, m2.off, op)?;
4579                    ip += m2.len;
4580                    anchor = ip;
4581                    break 'search2;
4582                }
4583
4584                if start3 < ip + m1.len + 3 {
4585                    if start3 >= ip + m1.len {
4586                        if start2 < ip + m1.len {
4587                            let correction = ip + m1.len - start2;
4588                            start2 += correction;
4589                            m2.start = start2;
4590                            m2.len = m2.len.saturating_sub(correction);
4591                            if m2.len < MINMATCH {
4592                                start2 = start3;
4593                                m2 = m3;
4594                            }
4595                        }
4596                        op = encode_sequence(full, dst, anchor, ip, m1.len, m1.off, op)?;
4597                        ip += m1.len;
4598                        anchor = ip;
4599
4600                        ip = start3;
4601                        m1 = m3;
4602                        start0 = start2;
4603                        m0 = m2;
4604                        continue 'search2;
4605                    }
4606
4607                    start2 = start3;
4608                    m2 = m3;
4609                    continue 'search3;
4610                }
4611
4612                if start2 < ip + m1.len {
4613                    if start2 - ip < OPTIMAL_ML {
4614                        if m1.len > OPTIMAL_ML {
4615                            m1.len = OPTIMAL_ML;
4616                        }
4617                        if ip + m1.len > start2 + m2.len - MINMATCH {
4618                            m1.len = start2 - ip + m2.len - MINMATCH;
4619                        }
4620                        let correction = m1.len.saturating_sub(start2 - ip);
4621                        if correction > 0 {
4622                            start2 += correction;
4623                            m2.start = start2;
4624                            m2.len -= correction;
4625                        }
4626                    } else {
4627                        m1.len = start2 - ip;
4628                    }
4629                }
4630
4631                op = encode_sequence(full, dst, anchor, ip, m1.len, m1.off, op)?;
4632                ip += m1.len;
4633                anchor = ip;
4634
4635                ip = start2;
4636                m1 = m2;
4637                start2 = start3;
4638                m2 = m3;
4639                continue 'search3;
4640            }
4641        }
4642    }
4643
4644    emit_last_literals_with_base(full, dst, base, anchor, op)
4645}
4646
4647/// HC block compressor with a preceding dictionary. Concatenates the
4648/// (truncated to `LZ4_DISTANCE_MAX`) dictionary in front of `src` and
4649/// dispatches to the matching HC strategy (LZ4mid / hash-chain / optimal),
4650/// mirroring upstream `LZ4HC_compress_generic_internal()` with
4651/// `dictDirective == usingDictCtx`/`usingExtDict`.
4652fn compress_block_hc_with_dict(
4653    src: &[u8],
4654    dst: &mut [u8],
4655    dict: &[u8],
4656    compression_level: c_int,
4657    favor_dec_speed: bool,
4658) -> Option<usize> {
4659    if src.is_empty() {
4660        return emit_last_literals(src, dst, 0, 0);
4661    }
4662    let dict_keep = cmp::min(dict.len(), LZ4_DISTANCE_MAX);
4663    let dict = &dict[dict.len() - dict_keep..];
4664    if dict.is_empty() || src.len() < MFLIMIT + 1 {
4665        return compress_block_hc(src, dst, compression_level, favor_dec_speed);
4666    }
4667
4668    let mut full = Vec::with_capacity(dict.len() + src.len());
4669    full.extend_from_slice(dict);
4670    full.extend_from_slice(src);
4671    let base = dict.len();
4672    let level = normalize_hc_level(compression_level);
4673    if level <= 2 {
4674        return compress_block_lz4mid_with_base(&full, dst, base);
4675    }
4676    if level >= 10 {
4677        return compress_block_hc_optimal(&full, dst, base, level, favor_dec_speed);
4678    }
4679    compress_block_hc_hashchain(&full, dst, base, level)
4680}
4681
4682/// HC optimal parser (levels 10-12). Mirrors upstream `LZ4HC_compress_optimal`
4683/// (lz4hc.c:1879+): builds a forward-DP `opt[]` table of best-price
4684/// (literal, match) decisions over a sliding window of up to `LZ4_OPT_NUM`
4685/// positions, then traces back to emit the chosen sequences. `base` is the
4686/// offset inside `full` where the input to be emitted starts; `full[..base]`
4687/// is retained dictionary prefix (or empty). `favor_dec_speed` enables the
4688/// upstream `favorDecompressionSpeed` shortcut that biases matching towards
4689/// faster decode at a small ratio cost.
4690fn compress_block_hc_optimal(
4691    full: &[u8],
4692    dst: &mut [u8],
4693    base: usize,
4694    compression_level: c_int,
4695    favor_dec_speed: bool,
4696) -> Option<usize> {
4697    match (normalize_hc_level(compression_level), favor_dec_speed) {
4698        (10, false) => compress_block_hc_optimal_impl::<96, 64, false, false>(full, dst, base),
4699        (10, true) => compress_block_hc_optimal_impl::<96, 64, false, true>(full, dst, base),
4700        (11, false) => compress_block_hc_optimal_impl::<512, 128, false, false>(full, dst, base),
4701        (11, true) => compress_block_hc_optimal_impl::<512, 128, false, true>(full, dst, base),
4702        (_, false) => compress_block_hc_optimal_impl::<16_384, { LZ4_OPT_NUM - 1 }, true, false>(
4703            full, dst, base,
4704        ),
4705        (_, true) => compress_block_hc_optimal_impl::<16_384, { LZ4_OPT_NUM - 1 }, true, true>(
4706            full, dst, base,
4707        ),
4708    }
4709}
4710
4711fn compress_block_hc_optimal_impl<
4712    const ATTEMPTS: usize,
4713    const SUFFICIENT_LEN: usize,
4714    const FULL_UPDATE: bool,
4715    const FAVOR_DEC_SPEED: bool,
4716>(
4717    full: &[u8],
4718    dst: &mut [u8],
4719    base: usize,
4720) -> Option<usize> {
4721    let src_len = full.len().checked_sub(base)?;
4722    if src_len == 0 {
4723        return emit_last_literals_with_base(full, dst, base, base, 0);
4724    }
4725    if src_len < MFLIMIT + 1 {
4726        return emit_last_literals_with_base(full, dst, base, base, 0);
4727    }
4728
4729    let mut table = HcTables::with_base(full.len(), base);
4730    if base > 0 {
4731        table.insert_until(full, base);
4732    }
4733
4734    // Allocate on the heap (Box<[_; N]>) instead of `Vec` so the
4735    // compiler knows the length is fixed at `LZ4_OPT_NUM + 3` and can
4736    // eliminate per-access bounds checks more aggressively. We keep it on
4737    // the heap rather than the stack because the array is 64 KiB
4738    // (4099 × 16 B), which is well above what's safe on the default 8 MiB
4739    // stack when we're already deep in the encoder call stack.
4740    let mut opt: Box<[HcOptimal; LZ4_OPT_NUM + 3]> = vec![
4741        HcOptimal {
4742            price: u32::MAX / 4,
4743            off: 0,
4744            mlen: 1,
4745            litlen: 0,
4746        };
4747        LZ4_OPT_NUM + 3
4748    ]
4749    .into_boxed_slice()
4750    .try_into()
4751    .unwrap();
4752
4753    let mut ip = base;
4754    let mut anchor = base;
4755    let mut op = 0usize;
4756    let iend = full.len();
4757    let mflimit = iend - MFLIMIT;
4758    let match_limit = iend - LAST_LITERALS;
4759    // High-water mark of the largest opt[] index written by the previous
4760    // main-loop iteration. The next iteration only needs to reset the
4761    // entries it might read. On the first iteration we conservatively clear
4762    // the full table so pre-loop sentinel values are correct everywhere.
4763    let mut prev_max_touched: usize = LZ4_OPT_NUM + 2;
4764
4765    while ip <= mflimit {
4766        let llen = ip - anchor;
4767        let first_match = find_hc_longer_match::<FAVOR_DEC_SPEED>(
4768            full,
4769            &mut table,
4770            ip,
4771            match_limit,
4772            MINMATCH - 1,
4773            ATTEMPTS,
4774        );
4775        if first_match.len == 0 {
4776            ip += 1;
4777            continue;
4778        }
4779
4780        if first_match.len > SUFFICIENT_LEN {
4781            op = encode_sequence(full, dst, anchor, ip, first_match.len, first_match.off, op)?;
4782            ip += first_match.len;
4783            anchor = ip;
4784            continue;
4785        }
4786
4787        // Reset only the prefix of opt[] that the previous iteration
4788        // touched. Profile showed the unconditional 65-KiB
4789        // `opt.fill(...)` was burning a huge chunk of HC10 time
4790        // (visible as many `movdqu %xmm0` writes in `perf annotate`).
4791        // The largest index any iteration writes is `last_match_pos + 3`
4792        // (the trailing add_lit loop), so resetting that range plus a
4793        // little slack is sufficient for the next iteration's reads to
4794        // see the sentinel everywhere they care about.
4795        let fill_end = (prev_max_touched + 1).min(LZ4_OPT_NUM + 3);
4796        opt[..fill_end].fill(HcOptimal {
4797            price: u32::MAX / 4,
4798            off: 0,
4799            mlen: 1,
4800            litlen: 0,
4801        });
4802
4803        for rpos in 0..MINMATCH {
4804            opt[rpos] = HcOptimal {
4805                price: hc_literals_price(llen + rpos),
4806                off: 0,
4807                mlen: 1,
4808                litlen: (llen + rpos) as u32,
4809            };
4810        }
4811
4812        let first_len = first_match.len.min(LZ4_OPT_NUM - 4);
4813        for mlen in MINMATCH..=first_len {
4814            opt[mlen] = HcOptimal {
4815                price: hc_sequence_price(llen, mlen),
4816                off: first_match.off as u32,
4817                mlen: mlen as u32,
4818                litlen: llen as u32,
4819            };
4820        }
4821        let mut last_match_pos = first_len;
4822        for add_lit in 1..=3 {
4823            let pos = last_match_pos + add_lit;
4824            opt[pos] = HcOptimal {
4825                price: opt[last_match_pos].price + hc_literals_price(add_lit),
4826                off: 0,
4827                mlen: 1,
4828                litlen: add_lit as u32,
4829            };
4830        }
4831
4832        let mut best_mlen = opt[last_match_pos].mlen as usize;
4833        let mut best_off = opt[last_match_pos].off as usize;
4834        let mut cur = 1usize;
4835        let mut immediate = false;
4836
4837        while cur < last_match_pos {
4838            let cur_ptr = ip + cur;
4839            if cur_ptr > mflimit {
4840                break;
4841            }
4842
4843            if FULL_UPDATE {
4844                if opt[cur + 1].price <= opt[cur].price
4845                    && opt[cur + MINMATCH].price < opt[cur].price + 3
4846                {
4847                    cur += 1;
4848                    continue;
4849                }
4850            } else if opt[cur + 1].price <= opt[cur].price {
4851                cur += 1;
4852                continue;
4853            }
4854
4855            let min_len = if FULL_UPDATE {
4856                MINMATCH - 1
4857            } else {
4858                last_match_pos - cur
4859            };
4860            let new_match = find_hc_longer_match::<FAVOR_DEC_SPEED>(
4861                full,
4862                &mut table,
4863                cur_ptr,
4864                match_limit,
4865                min_len,
4866                ATTEMPTS,
4867            );
4868            if new_match.len == 0 {
4869                cur += 1;
4870                continue;
4871            }
4872
4873            if new_match.len > SUFFICIENT_LEN || new_match.len + cur >= LZ4_OPT_NUM {
4874                best_mlen = new_match.len;
4875                best_off = new_match.off;
4876                last_match_pos = cur + 1;
4877                immediate = true;
4878                break;
4879            }
4880
4881            let base_litlen = opt[cur].litlen as usize;
4882            // Hoist `opt[cur].price` and `hc_literals_price(base_litlen)` out
4883            // of the 3-iter literals loop — both are loop-invariant.
4884            let lit_anchor_price = opt[cur]
4885                .price
4886                .saturating_sub(hc_literals_price(base_litlen));
4887            for litlen in 1..MINMATCH {
4888                let pos = cur + litlen;
4889                let price = lit_anchor_price + hc_literals_price(base_litlen + litlen);
4890                if price < opt[pos].price {
4891                    opt[pos] = HcOptimal {
4892                        price,
4893                        off: 0,
4894                        mlen: 1,
4895                        litlen: (base_litlen + litlen) as u32,
4896                    };
4897                }
4898            }
4899
4900            let match_len = new_match.len.min(LZ4_OPT_NUM - cur - 1);
4901            // Hoist the loop-invariant `opt[cur].mlen == 1` decision out of
4902            // the ml loop. `base_ll` and `base_price` don't depend on ml, so
4903            // computing them once instead of on every inner iteration cuts
4904            // both the cache pressure on `opt[cur]` and the cmov/branch
4905            // overhead. Mirrors upstream's structure where `ll` and the
4906            // prefix lookup are read once before the price loop.
4907            let (base_ll, base_price) = if opt[cur].mlen == 1 {
4908                let ll = opt[cur].litlen as usize;
4909                let prefix = if cur > ll { opt[cur - ll].price } else { 0 };
4910                (ll, prefix)
4911            } else {
4912                (0usize, opt[cur].price)
4913            };
4914            let new_off_u32 = new_match.off as u32;
4915            let base_ll_u32 = base_ll as u32;
4916            let dec_speed_adj = u32::from(FAVOR_DEC_SPEED);
4917            // Hoist `hc_sequence_price`'s literals-dependent portion out of
4918            // the loop. Only the match-length extension term needs to be
4919            // recomputed per iteration, and only for `ml >= 19` (the common
4920            // short-match case skips this entirely).
4921            let base_seq_price = base_price + 3 + hc_literals_price(base_ll);
4922            // Split the inner ml loop. `last_match_pos` only changes when
4923            // `ml == match_len` in the LAST iteration, so for ml in
4924            // MINMATCH..match_len it is invariant — let the compiler see
4925            // that by lifting the `last_match_pos + 3` threshold out of the
4926            // body and removing the `ml == match_len` branch from the hot
4927            // loop entirely.
4928            let lmp_thresh = last_match_pos + 3;
4929            for ml in MINMATCH..match_len {
4930                let pos = cur + ml;
4931                let mut price = base_seq_price;
4932                if ml >= 15 + MINMATCH {
4933                    price += 1 + ((ml - (15 + MINMATCH)) / 255) as u32;
4934                }
4935                // Lift the `opt[pos].price` read out of the unconditional
4936                // path. When `pos > lmp_thresh` we always write, so the
4937                // existing price doesn't matter — and skipping the read
4938                // saves one cache hit per iteration in the typical case
4939                // (most ml iters land past the threshold for HC10/11).
4940                let do_write = if pos > lmp_thresh {
4941                    true
4942                } else {
4943                    let acceptable_price = opt[pos].price.saturating_sub(dec_speed_adj);
4944                    price <= acceptable_price
4945                };
4946                if do_write {
4947                    opt[pos] = HcOptimal {
4948                        price,
4949                        off: new_off_u32,
4950                        mlen: ml as u32,
4951                        litlen: base_ll_u32,
4952                    };
4953                }
4954            }
4955            // Handle the final iteration (ml == match_len) which also
4956            // potentially updates `last_match_pos`.
4957            if match_len >= MINMATCH {
4958                let ml = match_len;
4959                let pos = cur + ml;
4960                let mut price = base_seq_price;
4961                if ml >= 15 + MINMATCH {
4962                    price += 1 + ((ml - (15 + MINMATCH)) / 255) as u32;
4963                }
4964                let do_write = if pos > last_match_pos + 3 {
4965                    true
4966                } else {
4967                    let acceptable_price = opt[pos].price.saturating_sub(dec_speed_adj);
4968                    price <= acceptable_price
4969                };
4970                if do_write {
4971                    if last_match_pos < pos {
4972                        last_match_pos = pos;
4973                    }
4974                    opt[pos] = HcOptimal {
4975                        price,
4976                        off: new_off_u32,
4977                        mlen: ml as u32,
4978                        litlen: base_ll_u32,
4979                    };
4980                }
4981            }
4982
4983            for add_lit in 1..=3 {
4984                let pos = last_match_pos + add_lit;
4985                opt[pos] = HcOptimal {
4986                    price: opt[last_match_pos].price + hc_literals_price(add_lit),
4987                    off: 0,
4988                    mlen: 1,
4989                    litlen: add_lit as u32,
4990                };
4991            }
4992            cur += 1;
4993        }
4994
4995        if !immediate {
4996            best_mlen = opt[last_match_pos].mlen as usize;
4997            best_off = opt[last_match_pos].off as usize;
4998            cur = last_match_pos.saturating_sub(best_mlen);
4999        }
5000
5001        let mut candidate_pos = cur;
5002        let mut selected_match_length = best_mlen as u32;
5003        let mut selected_offset = best_off as u32;
5004        loop {
5005            let next_match_length = opt[candidate_pos].mlen;
5006            let next_offset = opt[candidate_pos].off;
5007            opt[candidate_pos].mlen = selected_match_length;
5008            opt[candidate_pos].off = selected_offset;
5009            selected_match_length = next_match_length;
5010            selected_offset = next_offset;
5011            if next_match_length as usize > candidate_pos {
5012                break;
5013            }
5014            candidate_pos -= next_match_length as usize;
5015        }
5016
5017        let mut rpos = 0usize;
5018        while rpos < last_match_pos {
5019            let ml = opt[rpos].mlen as usize;
5020            let offset = opt[rpos].off as usize;
5021            if ml == 1 {
5022                ip += 1;
5023                rpos += 1;
5024                continue;
5025            }
5026            rpos += ml;
5027            op = encode_sequence(full, dst, anchor, ip, ml, offset, op)?;
5028            ip += ml;
5029            anchor = ip;
5030        }
5031        // Track how much of opt[] we wrote to so the next iteration's
5032        // partial fill knows what to reset. The largest-index write is
5033        // the trailing `add_lit` loop at `last_match_pos + 3`.
5034        prev_max_touched = last_match_pos + 3;
5035    }
5036
5037    emit_last_literals_with_base(full, dst, base, anchor, op)
5038}
5039
5040/// Compress a single frame-format block under the given preferences. Selects
5041/// HC (with or without dictionary) when `prefs.compression_level` is at least
5042/// the frame HC minimum, and the fast LZ4 block compressor otherwise. Used by
5043/// the frame-format encoder.
5044fn compress_frame_block(
5045    src: &[u8],
5046    dst: &mut [u8],
5047    prefs: &FramePrefs,
5048    dict: &[u8],
5049) -> Option<usize> {
5050    if prefs.compression_level >= LZ4HC_CLEVEL_MIN && !dict.is_empty() {
5051        compress_block_hc_with_dict(
5052            src,
5053            dst,
5054            dict,
5055            prefs.compression_level,
5056            prefs.favor_dec_speed,
5057        )
5058    } else if prefs.compression_level >= LZ4HC_CLEVEL_MIN {
5059        compress_block_hc(src, dst, prefs.compression_level, prefs.favor_dec_speed)
5060    } else {
5061        compress_block(src, dst, 1)
5062    }
5063}
5064
5065/// Clamp an HC compression level to `[1, LZ4HC_CLEVEL_MAX]`; non-positive
5066/// values map to `LZ4HC_CLEVEL_DEFAULT`. Mirrors upstream's `LZ4HC_CLEVEL_*`
5067/// validation in `LZ4HC_compress_generic_internal`.
5068fn normalize_hc_level(compression_level: c_int) -> c_int {
5069    if compression_level < 1 {
5070        LZ4HC_CLEVEL_DEFAULT
5071    } else {
5072        cmp::min(compression_level, LZ4HC_CLEVEL_MAX)
5073    }
5074}
5075
5076/// Byte cost of a literals run of length `lit_len` in the LZ4 wire format.
5077/// Mirrors upstream `LZ4HC_literalsPrice` (lz4hc.c:1827): each literal byte
5078/// costs 1, plus one extra byte for entering the variable-length encoding at
5079/// `lit_len >= RUN_MASK` and one further byte per 255-byte chunk.
5080#[inline(always)]
5081fn hc_literals_price(lit_len: usize) -> u32 {
5082    let mut price = lit_len as u32;
5083    if lit_len >= 15 {
5084        price += 1 + ((lit_len - 15) / 255) as u32;
5085    }
5086    price
5087}
5088
5089/// Byte cost of an encoded (literals, match) sequence. Mirrors upstream
5090/// `LZ4HC_sequencePrice` (lz4hc.c:1837): one token byte, two offset bytes,
5091/// the literals price, and any extension bytes for matches beyond
5092/// `ML_MASK + MINMATCH`.
5093#[inline(always)]
5094fn hc_sequence_price(lit_len: usize, match_len: usize) -> u32 {
5095    let mut price = 1 + 2 + hc_literals_price(lit_len);
5096    if match_len >= 15 + MINMATCH {
5097        price += 1 + ((match_len - (15 + MINMATCH)) / 255) as u32;
5098    }
5099    price
5100}
5101
5102/// HC `fillOutput`-style compressor: returns the largest `(consumed, written)`
5103/// pair such that the HC encoder fits inside `dst`. Unlike the fast block path
5104/// (`compress_dest_size`), upstream HC does not have a single-pass fillOutput
5105/// implementation, so we binary-search over prefixes of `src` and keep the
5106/// best fit. Used by `LZ4F_compressFrame` / streaming HC when only a bounded
5107/// output buffer is available.
5108fn compress_hc_dest_size(
5109    src: &[u8],
5110    dst: &mut [u8],
5111    compression_level: c_int,
5112    favor_dec_speed: bool,
5113) -> Option<(usize, usize)> {
5114    if src.is_empty() {
5115        let written = compress_block_hc(src, dst, compression_level, favor_dec_speed)?;
5116        return Some((0, written));
5117    }
5118
5119    let mut low = 0usize;
5120    let mut high = src.len();
5121    let mut best: Option<(usize, usize, Vec<u8>)> = None;
5122    while low <= high {
5123        let mid = low + (high - low) / 2;
5124        let mut candidate = vec![0u8; dst.len()];
5125        match compress_block_hc(
5126            &src[..mid],
5127            &mut candidate,
5128            compression_level,
5129            favor_dec_speed,
5130        ) {
5131            Some(written) if written <= dst.len() => {
5132                best = Some((mid, written, candidate));
5133                low = mid + 1;
5134            }
5135            _ => {
5136                if mid == 0 {
5137                    break;
5138                }
5139                high = mid - 1;
5140            }
5141        }
5142    }
5143
5144    let (consumed, written, candidate) = best?;
5145    dst[..written].copy_from_slice(&candidate[..written]);
5146    Some((consumed, written))
5147}
5148
5149/// HC `fillOutput`-style compressor with a preceding dictionary. Same prefix
5150/// binary search as `compress_hc_dest_size`, but each candidate call routes
5151/// through `compress_block_hc_with_dict` so the dictionary is in scope.
5152fn compress_hc_dest_size_with_dict(
5153    src: &[u8],
5154    dst: &mut [u8],
5155    dict: &[u8],
5156    compression_level: c_int,
5157    favor_dec_speed: bool,
5158) -> Option<(usize, usize)> {
5159    if src.is_empty() {
5160        let written =
5161            compress_block_hc_with_dict(src, dst, dict, compression_level, favor_dec_speed)?;
5162        return Some((0, written));
5163    }
5164
5165    let mut low = 0usize;
5166    let mut high = src.len();
5167    let mut best: Option<(usize, usize, Vec<u8>)> = None;
5168    while low <= high {
5169        let mid = low + (high - low) / 2;
5170        let mut candidate = vec![0u8; dst.len()];
5171        match compress_block_hc_with_dict(
5172            &src[..mid],
5173            &mut candidate,
5174            dict,
5175            compression_level,
5176            favor_dec_speed,
5177        ) {
5178            Some(written) if written <= dst.len() => {
5179                best = Some((mid, written, candidate));
5180                low = mid + 1;
5181            }
5182            _ => {
5183                if mid == 0 {
5184                    break;
5185                }
5186                high = mid - 1;
5187            }
5188        }
5189    }
5190
5191    let (consumed, written, candidate) = best?;
5192    dst[..written].copy_from_slice(&candidate[..written]);
5193    Some((consumed, written))
5194}
5195
5196/// Append freshly-compressed input to the HC streaming dictionary, dropping
5197/// from the front so the retained tail never exceeds `LZ4_DISTANCE_MAX` —
5198/// matching upstream's sliding-window behavior in `LZ4_loadDictHC` /
5199/// `LZ4HC_setExternalDict`.
5200fn append_hc_dictionary(dictionary: &mut Vec<u8>, src: &[u8]) {
5201    dictionary.extend_from_slice(src);
5202    if dictionary.len() > LZ4_DISTANCE_MAX {
5203        let drop_len = dictionary.len() - LZ4_DISTANCE_MAX;
5204        dictionary.drain(..drop_len);
5205    }
5206}
5207
5208/// Insert positions up to `ip` into the HC chain and return the best forward
5209/// match starting at `ip`. Mirrors upstream `LZ4HC_InsertAndFindBestMatch`
5210/// (lz4hc.c:1139): pins `iLowLimit == ip`, disables chain swapping, and
5211/// enables pattern analysis only when the search budget exceeds 128 (levels
5212/// 9+). Used by the plain HC hash-chain parser.
5213fn find_hc_match<const PATTERN_ANALYSIS: bool>(
5214    src: &[u8],
5215    table: &mut HcTables,
5216    ip: usize,
5217    match_limit: usize,
5218    max_attempts: usize,
5219) -> HcMatch {
5220    find_hc_wider_match::<PATTERN_ANALYSIS, false, false>(
5221        src,
5222        table,
5223        ip,
5224        ip,
5225        match_limit,
5226        MINMATCH - 1,
5227        max_attempts,
5228    )
5229}
5230
5231/// Match-finder used by the HC optimal parser: returns the best forward match
5232/// at `ip` that strictly exceeds `min_len`, or `len == 0` if none was found.
5233/// Mirrors upstream `LZ4HC_FindLongerMatch` (lz4hc.c:1851): always enables
5234/// pattern analysis and chain-swap, and applies the `favorDecSpeed` shortcut
5235/// that clamps match lengths in the `(18, 36]` band down to 18.
5236#[inline(always)]
5237fn find_hc_longer_match<const FAVOR_DEC_SPEED: bool>(
5238    src: &[u8],
5239    table: &mut HcTables,
5240    ip: usize,
5241    match_limit: usize,
5242    min_len: usize,
5243    max_attempts: usize,
5244) -> HcMatch {
5245    let m = find_hc_wider_match::<true, true, FAVOR_DEC_SPEED>(
5246        src,
5247        table,
5248        ip,
5249        ip,
5250        match_limit,
5251        min_len,
5252        max_attempts,
5253    );
5254    if m.len <= min_len {
5255        HcMatch {
5256            start: ip,
5257            len: 0,
5258            off: 0,
5259        }
5260    } else {
5261        let len = if FAVOR_DEC_SPEED && m.len > 18 && m.len <= 36 {
5262            18
5263        } else {
5264            m.len
5265        };
5266        HcMatch { len, ..m }
5267    }
5268}
5269
5270/// Core HC match finder. Mirrors upstream `LZ4HC_InsertAndGetWiderMatch`
5271/// (lz4hc.c:914+): walks the hash chain backwards from `ip` for up to
5272/// `max_attempts` steps, optionally allowing matches that start before `ip`
5273/// (i.e. `iLowLimit < ip`) so the parser can lengthen an earlier match. The
5274/// `flags` bitmask selects pattern analysis, chain swap, and the
5275/// `favorDecompressionSpeed` shortcut that skips matches with offset < 8.
5276#[inline(always)]
5277fn find_hc_wider_match<
5278    const PATTERN_ANALYSIS: bool,
5279    const CHAIN_SWAP: bool,
5280    const FAVOR_DEC_SPEED: bool,
5281>(
5282    src: &[u8],
5283    table: &mut HcTables,
5284    ip: usize,
5285    low_limit: usize,
5286    match_limit: usize,
5287    longest: usize,
5288    max_attempts: usize,
5289) -> HcMatch {
5290    table.insert_until(src, ip);
5291    if ip + MINMATCH > match_limit {
5292        return HcMatch {
5293            start: ip,
5294            len: 0,
5295            off: 0,
5296        };
5297    }
5298
5299    let ip_log = HcTables::to_log(ip);
5300    // `ipIndex - LZ4_DISTANCE_MAX`, clamped at `HC_OFFSET`: below `HC_OFFSET`
5301    // means "before the start of valid data". Matches upstream's
5302    // `max(lowLimit, ipIndex - LZ4_DISTANCE_MAX)`.
5303    let lowest_log = cmp::max(HC_OFFSET, ip_log.wrapping_sub(LZ4_DISTANCE_MAX as u32));
5304    let mut candidate_log = table.hash[hash4_hc(src, ip)];
5305    let mut attempts = max_attempts;
5306    let mut best = HcMatch {
5307        start: ip,
5308        len: longest,
5309        off: 0,
5310    };
5311    let pattern = read_u32(&src[ip..]);
5312    let repeated_pattern = is_repeated_pattern(pattern);
5313    let mut src_pattern_len = 0usize;
5314    let mut match_chain_pos = 0usize;
5315    let src_ptr = src.as_ptr();
5316    let look_back = ip - low_limit;
5317    let prefix_base = table.base;
5318    // `HcTables::with_base` always allocates `LZ4_DISTANCE_MAX + 1` entries,
5319    // mirroring upstream's fixed-size `chainTable[LZ4HC_MAXD]`.
5320    let chain_ptr = table.chain.as_ptr();
5321    // Upstream loop condition is exactly this after the `+64 KiB` offset
5322    // trick: `while ((matchIndex >= lowestMatchIndex) && (nbAttempts > 0))`.
5323    // The `u16` delta chain + logical positions keep any wrap-around
5324    // impossible, so no extra `candidate < ip` or sentinel guards are needed.
5325    while candidate_log >= lowest_log && attempts > 0 {
5326        attempts -= 1;
5327        let candidate = HcTables::to_phys(candidate_log);
5328        let mut match_len = 0usize;
5329        // Combine the two `if !early_skip` blocks into a single guarded
5330        // region. With `#[inline(always)]` and `favor_dec_speed=false` (the
5331        // common HC10-12 case), the compiler will DCE the entire block
5332        // wrapper. Mirrors upstream's structure where the early-exit filter
5333        // and the 4-byte pattern compare share one guarded block.
5334        //
5335        // Bound checks `ref_end < src_len && cand_end < src_len` were
5336        // removed earlier — both are guaranteed by the loop preconditions
5337        // (`low_limit + best.len ≤ match_limit ≤ src_len - LAST_LITERALS`
5338        // and `candidate >= look_back`).
5339        let early_skip = FAVOR_DEC_SPEED && ip - candidate < 8;
5340        if !early_skip {
5341            let mut passes_filter = true;
5342            if best.len >= 1 && candidate >= look_back {
5343                let ref_end = low_limit + best.len;
5344                let cand_end = candidate - look_back + best.len;
5345                let a = read_u16_ptr(unsafe { src_ptr.add(ref_end - 1) });
5346                let b = read_u16_ptr(unsafe { src_ptr.add(cand_end - 1) });
5347                passes_filter = a == b;
5348            }
5349            if passes_filter && read_u32_ptr(unsafe { src_ptr.add(candidate) }) == pattern {
5350                let forward =
5351                    MINMATCH + count_match(src, ip + MINMATCH, candidate + MINMATCH, match_limit);
5352                let back = count_back(src, ip, candidate, low_limit);
5353                let len = forward + back;
5354                match_len = len;
5355                if len > best.len {
5356                    best = HcMatch {
5357                        start: ip - back,
5358                        len,
5359                        off: ip - candidate,
5360                    };
5361                }
5362            }
5363        }
5364
5365        if CHAIN_SWAP && match_len == best.len && candidate + best.len <= ip {
5366            let mut distance_to_next: u32 = 1;
5367            let end = best.len.saturating_sub(MINMATCH - 1);
5368            let mut accel = 1usize << 4;
5369            let mut pos = 0usize;
5370            while pos < end {
5371                let probe = candidate + pos;
5372                // Chain stores deltas directly, mirroring upstream's
5373                // `DELTANEXTU16` return value. `0xFFFF` (init sentinel) is
5374                // strictly greater than anything useful, but it naturally
5375                // caps `distance_to_next`'s growth.
5376                let candidate_dist = unsafe { *chain_ptr.add(probe & LZ4_DISTANCE_MAX) } as u32;
5377                let step = accel >> 4;
5378                accel += 1;
5379                if candidate_dist > distance_to_next {
5380                    distance_to_next = candidate_dist;
5381                    match_chain_pos = pos;
5382                    accel = 1usize << 4;
5383                }
5384                pos += step;
5385            }
5386            if distance_to_next > 1 {
5387                candidate_log = candidate_log.wrapping_sub(distance_to_next);
5388                continue;
5389            }
5390        }
5391
5392        let chain_probe = candidate + match_chain_pos;
5393        let dist_next_match = unsafe { *chain_ptr.add(chain_probe & LZ4_DISTANCE_MAX) } as u32;
5394        if PATTERN_ANALYSIS
5395            && dist_next_match == 1
5396            && repeated_pattern
5397            && candidate_log > lowest_log
5398            && match_chain_pos == 0
5399        {
5400            if src_pattern_len == 0 {
5401                src_pattern_len =
5402                    MINMATCH + count_pattern(src, ip + MINMATCH, match_limit, pattern);
5403            }
5404            let match_candidate = candidate - 1;
5405            // Upstream's `LZ4HC_protectDictEnd` guards the repeated-pattern
5406            // branch so a dict-area candidate that sits in the final 3 bytes
5407            // before the current prefix (i.e. would straddle the dict/prefix
5408            // boundary in ext-dict mode) is excluded. In our contiguous
5409            // buffer model this collapses to requiring
5410            // `match_candidate + 4 <= base` when the candidate is inside the
5411            // dict; candidates at or past `base` are always fine (upstream's
5412            // U32 wrap produces a value >= 3 in that case).
5413            let protect_dict_end = prefix_base == 0
5414                || match_candidate >= prefix_base
5415                || match_candidate + MINMATCH <= prefix_base;
5416            let lowest = HcTables::to_phys(lowest_log);
5417            if protect_dict_end
5418                && match_candidate >= lowest
5419                && match_candidate + MINMATCH <= src.len()
5420                && read_u32_ptr(unsafe { src.as_ptr().add(match_candidate) }) == pattern
5421            {
5422                let forward =
5423                    MINMATCH + count_pattern(src, match_candidate + MINMATCH, match_limit, pattern);
5424                // Upstream reverse-counts the pattern back to either
5425                // `prefixPtr` or `dictStart` depending on whether the
5426                // candidate is in the current prefix or external dictionary,
5427                // and then clamps the returned length so
5428                // `matchCandidateIdx - backLength >= lowestMatchIndex`. Our
5429                // contiguous-buffer model combines both bounds into the
5430                // single `0` floor for `reverseCountPattern`, so we just
5431                // apply the distance-based clamp afterwards.
5432                let back_raw = reverse_count_pattern(src, match_candidate, 0, pattern);
5433                let back = cmp::min(back_raw, match_candidate.saturating_sub(lowest));
5434                let current_segment_len = back + forward;
5435                let adjusted_to_segment_end =
5436                    current_segment_len >= src_pattern_len && forward <= src_pattern_len;
5437                let mut next_candidate = if adjusted_to_segment_end {
5438                    match_candidate + forward - src_pattern_len
5439                } else {
5440                    match_candidate.saturating_sub(back)
5441                };
5442                if next_candidate < lowest {
5443                    next_candidate = lowest;
5444                }
5445                if adjusted_to_segment_end {
5446                    if next_candidate < candidate {
5447                        candidate_log = HcTables::to_log(next_candidate);
5448                        continue;
5449                    }
5450                } else if low_limit == ip
5451                    && next_candidate < ip
5452                    && ip - next_candidate <= LZ4_DISTANCE_MAX
5453                {
5454                    let max_len = cmp::min(current_segment_len, src_pattern_len);
5455                    if max_len > best.len && !(FAVOR_DEC_SPEED && ip - next_candidate < 8) {
5456                        best = HcMatch {
5457                            start: ip,
5458                            len: max_len,
5459                            off: ip - next_candidate,
5460                        };
5461                    }
5462                    if next_candidate < candidate {
5463                        let after_pattern = table.previous(next_candidate);
5464                        candidate_log =
5465                            if after_pattern != usize::MAX && after_pattern < next_candidate {
5466                                HcTables::to_log(after_pattern)
5467                            } else {
5468                                HcTables::to_log(next_candidate)
5469                            };
5470                        continue;
5471                    }
5472                }
5473
5474                if next_candidate < candidate {
5475                    candidate_log = HcTables::to_log(next_candidate);
5476                    continue;
5477                }
5478            }
5479        }
5480
5481        // `candidate_log -= dist_next_match` can't wrap below `HC_OFFSET`
5482        // because `candidate_log >= lowest_log >= HC_OFFSET` (≥ 65536) and
5483        // `dist_next_match <= 65535`. If it drops below `lowest_log` the
5484        // outer loop condition exits on the next turn.
5485        candidate_log = candidate_log.wrapping_sub(dist_next_match);
5486    }
5487
5488    best
5489}
5490
5491/// Returns `true` if `pattern` (a 4-byte sample) is a repetition of a 1- or
5492/// 2-byte unit. Used by the HC parser to gate the pattern-analysis fast path,
5493/// matching upstream's `LZ4HC_countPattern` precondition that the pattern is
5494/// a length-1, length-2, or length-4 repeat (but not length-3).
5495#[inline(always)]
5496fn is_repeated_pattern(pattern: u32) -> bool {
5497    (pattern & 0xffff) == (pattern >> 16) && (pattern & 0xff) == (pattern >> 24)
5498}
5499
5500/// Forward-count bytes in `src` starting at `pos` that continue a repeated
5501/// pattern. Mirrors upstream `LZ4HC_countPattern` (lz4hc.c:851): `pattern`
5502/// must be a length-1, length-2, or length-4 repeat. Returns the number of
5503/// matching bytes (capped at `limit - pos`).
5504#[inline]
5505fn count_pattern(src: &[u8], mut pos: usize, limit: usize, pattern: u32) -> usize {
5506    let byte = pattern as u8;
5507    let start = pos;
5508    while pos < limit && src[pos] == byte {
5509        pos += 1;
5510    }
5511    pos - start
5512}
5513
5514/// Backward-count bytes preceding `pos` that continue a repeated pattern.
5515/// Mirrors upstream `LZ4HC_reverseCountPattern` (lz4hc.c:884): `pattern` must
5516/// be a length-1, length-2, or length-4 repeat, read using natural platform
5517/// endianness. The bulk loop reads 4 bytes at a time, which is correct for
5518/// length-4 repeats (e.g. `0xAABBCCDD`) where a per-byte walk would miss
5519/// matches that align on 4-byte boundaries.
5520fn reverse_count_pattern(src: &[u8], mut pos: usize, low_limit: usize, pattern: u32) -> usize {
5521    let start = pos;
5522    // Upstream takes `(BYTE*)(&pattern) + 3` then walks down — i.e. the byte
5523    // at the highest memory address of the u32, regardless of host endianness.
5524    // `to_ne_bytes()` preserves that memory order.
5525    let pattern_bytes = pattern.to_ne_bytes();
5526    while pos >= low_limit + 4 {
5527        let prev = read_u32_ptr(unsafe { src.as_ptr().add(pos - 4) });
5528        if prev != pattern {
5529            break;
5530        }
5531        pos -= 4;
5532    }
5533    let mut tail_idx = 3usize;
5534    while pos > low_limit {
5535        if src[pos - 1] != pattern_bytes[tail_idx] {
5536            break;
5537        }
5538        pos -= 1;
5539        if tail_idx == 0 {
5540            break;
5541        }
5542        tail_idx -= 1;
5543    }
5544    start - pos
5545}
5546
5547/// Count the number of bytes matching immediately *before* `ip` and
5548/// `candidate`. Mirrors upstream `LZ4HC_countBack` (lz4hc.c:202) but returns a
5549/// positive count rather than the negative offset upstream produces. The
5550/// 8-byte SWAR step uses `leading_zeros / 8` to locate the first differing
5551/// byte from the high end of the loaded word.
5552#[inline(always)]
5553fn count_back(src: &[u8], ip: usize, candidate: usize, low_limit: usize) -> usize {
5554    let mut back = 0usize;
5555    let max_back = cmp::min(ip - low_limit, candidate);
5556    let src_ptr = src.as_ptr();
5557    unsafe {
5558        while back + 8 <= max_back {
5559            let a = read_u64_ptr(src_ptr.add(ip - back - 8));
5560            let b = read_u64_ptr(src_ptr.add(candidate - back - 8));
5561            let diff = a ^ b;
5562            if diff == 0 {
5563                back += 8;
5564            } else {
5565                back += diff.leading_zeros() as usize / 8;
5566                return back;
5567            }
5568        }
5569        while back < max_back && *src_ptr.add(ip - back - 1) == *src_ptr.add(candidate - back - 1) {
5570            back += 1;
5571        }
5572    }
5573    back
5574}
5575
5576/// Count the number of bytes matching forward from `ip` and `match_pos`,
5577/// stopping at `limit`. Mirrors upstream `LZ4_count` (lz4.c:676): the bulk
5578/// loop reads 8 bytes at a time and locates the first differing byte via
5579/// `trailing_zeros / 8`, then a tail walk handles the final 0-7 bytes.
5580#[inline(always)]
5581fn count_match(src: &[u8], mut ip: usize, mut match_pos: usize, limit: usize) -> usize {
5582    let start = ip;
5583    let src_ptr = src.as_ptr();
5584    unsafe {
5585        while ip + 8 <= limit {
5586            let diff = read_u64_ptr(src_ptr.add(ip)) ^ read_u64_ptr(src_ptr.add(match_pos));
5587            if diff != 0 {
5588                return ip - start + (diff.trailing_zeros() as usize / 8);
5589            }
5590            ip += 8;
5591            match_pos += 8;
5592        }
5593        while ip < limit && *src_ptr.add(ip) == *src_ptr.add(match_pos) {
5594            ip += 1;
5595            match_pos += 1;
5596        }
5597    }
5598    ip - start
5599}
5600
5601/// Emit one (literals, offset, match-length) sequence into `dst`. Mirrors
5602/// upstream `LZ4HC_encodeSequence` (lz4hc.c:292): writes the token byte, any
5603/// literal-length extension bytes, the literal payload, the 16-bit little-
5604/// endian offset, and any match-length extension bytes. Returns the new write
5605/// cursor, or `None` if the sequence would overflow `dst`.
5606#[inline(always)]
5607fn encode_sequence(
5608    src: &[u8],
5609    dst: &mut [u8],
5610    anchor: usize,
5611    ip: usize,
5612    match_len: usize,
5613    offset: usize,
5614    mut op: usize,
5615) -> Option<usize> {
5616    if offset == 0 || offset > LZ4_DISTANCE_MAX {
5617        return None;
5618    }
5619    let lit_len = ip - anchor;
5620    let token_pos = op;
5621    if op >= dst.len() {
5622        return None;
5623    }
5624    op += 1;
5625    op = emit_len(dst, op, lit_len, 15)?;
5626    if op + lit_len + 2 > dst.len() {
5627        return None;
5628    }
5629    dst[op..op + lit_len].copy_from_slice(&src[anchor..ip]);
5630    op += lit_len;
5631    dst[op..op + 2].copy_from_slice(&(offset as u16).to_le_bytes());
5632    op += 2;
5633
5634    let ml_code = match_len - MINMATCH;
5635    dst[token_pos] = ((cmp::min(lit_len, 15) as u8) << 4) | cmp::min(ml_code, 15) as u8;
5636    emit_len(dst, op, ml_code, 15)
5637}
5638
5639/// Emit the trailing literals run (token + variable-length literal-length
5640/// extension + payload) that terminates an LZ4 block. Mirrors upstream's
5641/// `_last_literals` exit block (lz4.c:1298+) under the `notLimited` directive.
5642/// Returns the final output cursor, or `None` if `dst` cannot fit the run.
5643fn emit_last_literals(src: &[u8], dst: &mut [u8], anchor: usize, mut op: usize) -> Option<usize> {
5644    let lit_len = src.len() - anchor;
5645    if op >= dst.len() {
5646        return None;
5647    }
5648    let token_pos = op;
5649    op += 1;
5650    dst[token_pos] = (cmp::min(lit_len, 15) as u8) << 4;
5651    op = emit_len(dst, op, lit_len, 15)?;
5652    if op + lit_len > dst.len() {
5653        return None;
5654    }
5655    dst[op..op + lit_len].copy_from_slice(&src[anchor..]);
5656    Some(op + lit_len)
5657}
5658
5659/// Variant of `emit_last_literals` that accounts for a `base` offset inside
5660/// `full` (i.e. `full[..base]` is dictionary history that should not appear in
5661/// the literal payload). Used by the dict-aware compressors so the trailing
5662/// literals are read from the prefix-relative range, mirroring upstream's
5663/// pointer arithmetic when `dictDirective != noDict`.
5664fn emit_last_literals_with_base(
5665    full: &[u8],
5666    dst: &mut [u8],
5667    base: usize,
5668    anchor: usize,
5669    mut op: usize,
5670) -> Option<usize> {
5671    if anchor < base || anchor > full.len() {
5672        return None;
5673    }
5674    let lit_len = full.len() - anchor;
5675    if op >= dst.len() {
5676        return None;
5677    }
5678    let token_pos = op;
5679    op += 1;
5680    dst[token_pos] = (cmp::min(lit_len, 15) as u8) << 4;
5681    op = emit_len(dst, op, lit_len, 15)?;
5682    if op + lit_len > dst.len() {
5683        return None;
5684    }
5685    let literal_start = anchor - base;
5686    dst[op..op + lit_len]
5687        .copy_from_slice(&full[base + literal_start..base + literal_start + lit_len]);
5688    Some(op + lit_len)
5689}
5690
5691/// Emit the variable-length extension bytes for a literals or match-length
5692/// run, mirroring the inline encoding upstream uses inside
5693/// `LZ4HC_encodeSequence` and `LZ4_compress_generic_validated`: while
5694/// `len >= base`, write `0xFF` bytes for each 255 chunk and then the residue
5695/// `(len - base) % 255`. `base` is the inline-token cap (`RUN_MASK = 15`).
5696fn emit_len(dst: &mut [u8], mut op: usize, len: usize, base: usize) -> Option<usize> {
5697    if len >= base {
5698        let mut extra = len - base;
5699        while extra >= 255 {
5700            if op >= dst.len() {
5701                return None;
5702            }
5703            dst[op] = 255;
5704            op += 1;
5705            extra -= 255;
5706        }
5707        if op >= dst.len() {
5708            return None;
5709        }
5710        dst[op] = extra as u8;
5711        op += 1;
5712    }
5713    Some(op)
5714}
5715
5716// Mirrors upstream `FASTLOOP_SAFE_DISTANCE` (lz4.c:249). The fast decode loop
5717// requires this much headroom in both directions so it can wild-copy 32 bytes
5718// past the current cursor without checking on every token.
5719const FASTLOOP_SAFE_DISTANCE: usize = 64;
5720
5721/// Wild copy 32 bytes at a time, advancing past `dst_end`. Caller must ensure
5722/// the destination buffer has at least 32 bytes of room past `dst_end`, that
5723/// `src_ptr..` has at least `dst_end - dst_ptr` bytes, and that source and
5724/// destination either don't overlap or have an offset of at least 32.
5725///
5726/// Mirrors upstream `LZ4_wildCopy32` (lz4.c:518).
5727#[inline(always)]
5728unsafe fn wild_copy_32(mut dst_ptr: *mut u8, mut src_ptr: *const u8, dst_end: *mut u8) {
5729    loop {
5730        ptr::copy_nonoverlapping(src_ptr, dst_ptr, 16);
5731        ptr::copy_nonoverlapping(src_ptr.add(16), dst_ptr.add(16), 16);
5732        dst_ptr = dst_ptr.add(32);
5733        src_ptr = src_ptr.add(32);
5734        if dst_ptr >= dst_end {
5735            break;
5736        }
5737    }
5738}
5739
5740/// Variable-length read for the fast loop, only ever called with `base == 15`.
5741/// Same semantics as `read_len(src, ip, 15)` but lifted up here so the fast
5742/// loop can inline aggressively.
5743#[inline(always)]
5744fn read_len_fast(src: &[u8], ip: &mut usize) -> Option<usize> {
5745    let mut total: usize = 15;
5746    loop {
5747        if *ip >= src.len() {
5748            return None;
5749        }
5750        let b = src[*ip] as usize;
5751        *ip += 1;
5752        total = total.checked_add(b)?;
5753        if b != 255 {
5754            return Some(total);
5755        }
5756    }
5757}
5758
5759/// Decompress a single LZ4 block (no dictionary). Returns the number of bytes
5760/// written, or `None` on malformed input.
5761///
5762/// Mirrors the fast-path of upstream `LZ4_decompress_generic` (lz4.c:2071+,
5763/// the `LZ4_FAST_DEC_LOOP` variant) — wild-copies 32 bytes at a time as long
5764/// as both input and output have enough headroom, falling through to the
5765/// safe tail loop (`decompress_block_safe`) when room runs out.
5766#[inline]
5767fn decompress_block(src: &[u8], dst: &mut [u8]) -> Option<usize> {
5768    let oend = dst.len();
5769    let iend = src.len();
5770
5771    // Tiny outputs skip the fast loop and go straight to the safe loop.
5772    if oend < FASTLOOP_SAFE_DISTANCE {
5773        return decompress_block_safe(src, dst, 0, 0);
5774    }
5775
5776    let dst_ptr = dst.as_mut_ptr();
5777    let src_ptr = src.as_ptr();
5778    let mut ip = 0usize;
5779    let mut op = 0usize;
5780
5781    // Fast loop: while we have ≥ 64 bytes of output headroom and ≥ 17 bytes of
5782    // input headroom, we can wild-copy 32 bytes ahead without per-token checks.
5783    // Mirrors upstream `LZ4_FAST_DEC_LOOP` (lz4.c:2071+).
5784    while op + FASTLOOP_SAFE_DISTANCE <= oend && ip + 17 <= iend {
5785        let token = unsafe { *src_ptr.add(ip) } as usize;
5786        ip += 1;
5787        let lit_len_token = token >> 4;
5788
5789        // ----- Literals -----
5790        let lit_len = if lit_len_token < 15 {
5791            // Short literal: blind 16-byte copy. The 17-byte input headroom
5792            // and 64-byte output headroom guarantee in-bounds. Up to 14 junk
5793            // bytes past the literal end get overwritten by subsequent
5794            // literal/match writes.
5795            unsafe {
5796                ptr::copy_nonoverlapping(src_ptr.add(ip), dst_ptr.add(op), 16);
5797            }
5798            lit_len_token
5799        } else {
5800            // Long literal: decode extension, then either wild-copy (if there
5801            // is room for a 32-byte overshoot) or do a bounded copy. Either
5802            // way we then continue inline with this same token's match part.
5803            let lit_len = read_len_fast(src, &mut ip)?;
5804            if ip + lit_len + 32 <= iend && op + lit_len + 32 <= oend {
5805                unsafe {
5806                    wild_copy_32(dst_ptr.add(op), src_ptr.add(ip), dst_ptr.add(op + lit_len));
5807                }
5808            } else {
5809                if ip + lit_len > iend || op + lit_len > oend {
5810                    return None;
5811                }
5812                unsafe {
5813                    ptr::copy_nonoverlapping(src_ptr.add(ip), dst_ptr.add(op), lit_len);
5814                }
5815            }
5816            lit_len
5817        };
5818        ip += lit_len;
5819        op += lit_len;
5820
5821        // End-of-block: the last sequence in a block is literals only.
5822        if ip == iend {
5823            return Some(op);
5824        }
5825
5826        // ----- Offset -----
5827        if ip + 2 > iend {
5828            return None;
5829        }
5830        let offset =
5831            unsafe { (*src_ptr.add(ip) as usize) | ((*src_ptr.add(ip + 1) as usize) << 8) };
5832        ip += 2;
5833        if offset == 0 || offset > op {
5834            return None;
5835        }
5836
5837        // ----- Match length -----
5838        let match_len = if (token & 0x0F) == 15 {
5839            read_len_fast(src, &mut ip)? + MINMATCH
5840        } else {
5841            (token & 0x0F) + MINMATCH
5842        };
5843        if ip == iend {
5844            return None;
5845        }
5846
5847        // ----- Match copy -----
5848        if op + match_len + 32 > oend {
5849            // Match lands in the tail — use the bounded helper. After this
5850            // we may or may not still be in fast-loop range; the loop
5851            // condition decides on the next iteration.
5852            copy_match_no_dict(dst, &mut op, offset, match_len)?;
5853            continue;
5854        }
5855
5856        unsafe {
5857            let match_src = dst_ptr.add(op - offset);
5858            if offset >= 16 {
5859                // Non-overlapping (or far-enough): wild-copy 32-at-a-time.
5860                wild_copy_32(dst_ptr.add(op), match_src, dst_ptr.add(op + match_len));
5861                op += match_len;
5862            } else if offset >= 8 && match_len <= 18 {
5863                // Common short match: 8 + 8 + 2 = 18 unconditional bytes.
5864                // Mirrors upstream's fastpath at lz4.c:2150-2152.
5865                ptr::copy_nonoverlapping(match_src, dst_ptr.add(op), 8);
5866                ptr::copy_nonoverlapping(match_src.add(8), dst_ptr.add(op + 8), 8);
5867                ptr::copy_nonoverlapping(match_src.add(16), dst_ptr.add(op + 16), 2);
5868                op += match_len;
5869            } else {
5870                // Overlapping or short-with-small-offset match — drop to the
5871                // tested doubling-copy helper.
5872                copy_match_no_dict(dst, &mut op, offset, match_len)?;
5873            }
5874        }
5875    }
5876
5877    decompress_block_safe(src, dst, ip, op)
5878}
5879
5880/// Safe (bounds-checking) tail of single-block decompression. Called either
5881/// directly for tiny outputs or as the fallback when `decompress_block` runs
5882/// out of fast-loop headroom. `ip`/`op` are the input/output positions where
5883/// the fast loop left off.
5884///
5885/// Mirrors the safe-tail of upstream `LZ4_decompress_generic` (lz4.c).
5886#[inline]
5887fn decompress_block_safe(
5888    src: &[u8],
5889    dst: &mut [u8],
5890    mut ip: usize,
5891    mut op: usize,
5892) -> Option<usize> {
5893    while ip < src.len() {
5894        let token = src[ip];
5895        ip += 1;
5896
5897        let lit_len = read_len(src, &mut ip, (token >> 4) as usize)?;
5898        if ip + lit_len > src.len() || op + lit_len > dst.len() {
5899            return None;
5900        }
5901        unsafe {
5902            ptr::copy_nonoverlapping(src.as_ptr().add(ip), dst.as_mut_ptr().add(op), lit_len);
5903        }
5904        ip += lit_len;
5905        op += lit_len;
5906        if ip == src.len() {
5907            return Some(op);
5908        }
5909        if ip + 2 > src.len() {
5910            return None;
5911        }
5912        let offset = read_u16(&src[ip..]) as usize;
5913        ip += 2;
5914        if offset == 0 || offset > op {
5915            return None;
5916        }
5917        let match_len = read_len(src, &mut ip, (token & 0x0f) as usize)? + MINMATCH;
5918        if ip == src.len() {
5919            return None;
5920        }
5921        if op + match_len > dst.len() {
5922            return None;
5923        }
5924        copy_match_no_dict(dst, &mut op, offset, match_len)?;
5925    }
5926    Some(op)
5927}
5928
5929/// Decompress a single LZ4 block whose match offsets may reach back into an
5930/// external `dict` buffer (linked blocks / external dictionary mode).
5931///
5932/// Mirrors upstream `LZ4_decompress_generic` (lz4.c) with `dict == usingExtDict`.
5933#[inline]
5934fn decompress_block_with_dict(src: &[u8], dst: &mut [u8], dict: &[u8]) -> Option<usize> {
5935    let mut ip = 0usize;
5936    let mut op = 0usize;
5937    while ip < src.len() {
5938        let token = src[ip];
5939        ip += 1;
5940
5941        let lit_len = read_len(src, &mut ip, (token >> 4) as usize)?;
5942        if ip + lit_len > src.len() || op + lit_len > dst.len() {
5943            return None;
5944        }
5945        dst[op..op + lit_len].copy_from_slice(&src[ip..ip + lit_len]);
5946        ip += lit_len;
5947        op += lit_len;
5948        if ip == src.len() {
5949            return Some(op);
5950        }
5951        if ip + 2 > src.len() {
5952            return None;
5953        }
5954        let offset = read_u16(&src[ip..]) as usize;
5955        ip += 2;
5956        if offset == 0 || offset > op + dict.len() {
5957            return None;
5958        }
5959        let match_len = read_len(src, &mut ip, (token & 0x0f) as usize)? + MINMATCH;
5960        if ip == src.len() {
5961            return None;
5962        }
5963        if op + match_len > dst.len() {
5964            return None;
5965        }
5966        copy_match(dst, dict, &mut op, offset, match_len)?;
5967    }
5968    Some(op)
5969}
5970
5971/// Partial-decompression entry point: stop after producing exactly `target`
5972/// bytes of output (or earlier if the block ends).
5973///
5974/// Mirrors upstream `LZ4_decompress_safe_partial` (lz4.c) with no dictionary.
5975fn decompress_block_partial(src: &[u8], dst: &mut [u8], target: usize) -> Option<usize> {
5976    decompress_block_partial_with_dict(src, dst, target, &[])
5977}
5978
5979/// Partial-decompression with external dictionary support. Stops once `target`
5980/// output bytes have been written; mid-sequence literals/matches are truncated
5981/// rather than failing.
5982///
5983/// Mirrors upstream `LZ4_decompress_safe_partial_usingDict` (lz4.c).
5984fn decompress_block_partial_with_dict(
5985    src: &[u8],
5986    dst: &mut [u8],
5987    target: usize,
5988    dict: &[u8],
5989) -> Option<usize> {
5990    if target == 0 {
5991        return Some(0);
5992    }
5993    let target = cmp::min(target, dst.len());
5994
5995    let mut ip = 0usize;
5996    let mut op = 0usize;
5997    while ip < src.len() && op < target {
5998        let token = src[ip];
5999        ip += 1;
6000
6001        let lit_len = read_len(src, &mut ip, (token >> 4) as usize)?;
6002        let lit_copy = cmp::min(lit_len, target - op);
6003        if ip + lit_copy > src.len() {
6004            return None;
6005        }
6006        dst[op..op + lit_copy].copy_from_slice(&src[ip..ip + lit_copy]);
6007        op += lit_copy;
6008        if op == target {
6009            return Some(op);
6010        }
6011        if ip + lit_len > src.len() {
6012            return None;
6013        }
6014        ip += lit_len;
6015        if ip == src.len() {
6016            return Some(op);
6017        }
6018        if ip + 2 > src.len() {
6019            return None;
6020        }
6021        let offset = read_u16(&src[ip..]) as usize;
6022        ip += 2;
6023        if offset == 0 || offset > op + dict.len() {
6024            return None;
6025        }
6026        let match_len = read_len(src, &mut ip, (token & 0x0f) as usize)? + MINMATCH;
6027        let match_copy = cmp::min(match_len, target - op);
6028        copy_match(dst, dict, &mut op, offset, match_copy)?;
6029    }
6030    Some(op)
6031}
6032
6033/// Decompress until exactly `dst.len()` output bytes have been produced (i.e.
6034/// the caller knows the exact uncompressed size up front), reading from an
6035/// unbounded `*const u8` input. Returns the number of input bytes consumed.
6036///
6037/// Mirrors upstream `LZ4_decompress_fast` / `LZ4_decompress_fast_usingDict`
6038/// (lz4.c): the "exact output size" entry points that have no input length
6039/// limit and rely on the output size as the loop terminator.
6040fn decompress_block_exact_ptr(src: *const u8, dst: &mut [u8], dict: &[u8]) -> Option<usize> {
6041    let mut ip = 0usize;
6042    let mut op = 0usize;
6043    while op < dst.len() {
6044        let token = unsafe { *src.add(ip) };
6045        ip += 1;
6046
6047        let lit_len = read_len_ptr(src, &mut ip, (token >> 4) as usize)?;
6048        if op + lit_len > dst.len() {
6049            return None;
6050        }
6051        unsafe {
6052            ptr::copy_nonoverlapping(src.add(ip), dst.as_mut_ptr().add(op), lit_len);
6053        }
6054        ip += lit_len;
6055        op += lit_len;
6056        if op == dst.len() {
6057            return Some(ip);
6058        }
6059
6060        let offset = read_u16_ptr(unsafe { src.add(ip) }) as usize;
6061        ip += 2;
6062        if offset == 0 || offset > op + dict.len() {
6063            return None;
6064        }
6065        let match_len = read_len_ptr(src, &mut ip, (token & 0x0f) as usize)? + MINMATCH;
6066        if op + match_len > dst.len() {
6067            return None;
6068        }
6069        copy_match(dst, dict, &mut op, offset, match_len)?;
6070    }
6071    Some(ip)
6072}
6073
6074/// Variable-length integer reader for the unbounded-input decode path: no
6075/// length check on `src` because the caller guarantees enough room.
6076///
6077/// Mirrors upstream `read_variable_length` (lz4.c:1975) without the
6078/// `ilimit` check, as used by `LZ4_decompress_fast`.
6079fn read_len_ptr(src: *const u8, ip: &mut usize, mut len: usize) -> Option<usize> {
6080    if len == 15 {
6081        loop {
6082            let b = unsafe { *src.add(*ip) } as usize;
6083            *ip += 1;
6084            len = len.checked_add(b)?;
6085            if b != 255 {
6086                break;
6087            }
6088        }
6089    }
6090    Some(len)
6091}
6092
6093/// Copy a back-reference of `len` bytes ending at `*op` from `offset` bytes
6094/// behind, possibly reaching back into the external `dict`. Handles the
6095/// overlapping match case (offset < len) by doubling the copied prefix until
6096/// `len` bytes are written.
6097///
6098/// Mirrors upstream's match-copy path in `LZ4_decompress_generic` (lz4.c)
6099/// when `usingExtDict` is set.
6100#[inline]
6101fn copy_match(
6102    dst: &mut [u8],
6103    dict: &[u8],
6104    op: &mut usize,
6105    offset: usize,
6106    mut len: usize,
6107) -> Option<()> {
6108    if offset == 0 {
6109        return None;
6110    }
6111    if offset > *op {
6112        let dict_offset = offset - *op;
6113        if dict_offset > dict.len() {
6114            return None;
6115        }
6116        let dict_pos = dict.len() - dict_offset;
6117        let dict_len = cmp::min(len, dict.len() - dict_pos);
6118        if *op + dict_len > dst.len() {
6119            return None;
6120        }
6121        unsafe {
6122            ptr::copy_nonoverlapping(
6123                dict.as_ptr().add(dict_pos),
6124                dst.as_mut_ptr().add(*op),
6125                dict_len,
6126            );
6127        }
6128        *op += dict_len;
6129        len -= dict_len;
6130    }
6131
6132    if len == 0 {
6133        return Some(());
6134    }
6135    if offset > *op {
6136        return None;
6137    }
6138    if *op + len > dst.len() {
6139        return None;
6140    }
6141
6142    let first = cmp::min(offset, len);
6143    let src = *op - offset;
6144    if src + first > *op {
6145        return None;
6146    }
6147    unsafe {
6148        ptr::copy_nonoverlapping(dst.as_ptr().add(src), dst.as_mut_ptr().add(*op), first);
6149    }
6150    *op += first;
6151    len -= first;
6152
6153    let mut copied = first;
6154    while len > 0 {
6155        let chunk = cmp::min(copied, len);
6156        let src = *op - copied;
6157        unsafe {
6158            ptr::copy_nonoverlapping(dst.as_ptr().add(src), dst.as_mut_ptr().add(*op), chunk);
6159        }
6160        *op += chunk;
6161        len -= chunk;
6162        copied += chunk;
6163    }
6164    Some(())
6165}
6166
6167/// Copy a back-reference within `dst` (no external dictionary). Handles the
6168/// overlapping match case by doubling the copied prefix until `len` bytes
6169/// are written.
6170///
6171/// Mirrors the in-block match-copy path of upstream `LZ4_decompress_generic`
6172/// (lz4.c) when `dict == noDict`.
6173#[inline]
6174fn copy_match_no_dict(dst: &mut [u8], op: &mut usize, offset: usize, mut len: usize) -> Option<()> {
6175    if offset == 0 || offset > *op || *op + len > dst.len() {
6176        return None;
6177    }
6178
6179    let first = cmp::min(offset, len);
6180    let src = *op - offset;
6181    unsafe {
6182        ptr::copy_nonoverlapping(dst.as_ptr().add(src), dst.as_mut_ptr().add(*op), first);
6183    }
6184    *op += first;
6185    len -= first;
6186
6187    let mut copied = first;
6188    while len > 0 {
6189        let chunk = cmp::min(copied, len);
6190        let src = *op - copied;
6191        unsafe {
6192            ptr::copy_nonoverlapping(dst.as_ptr().add(src), dst.as_mut_ptr().add(*op), chunk);
6193        }
6194        *op += chunk;
6195        len -= chunk;
6196        copied += chunk;
6197    }
6198    Some(())
6199}
6200
6201/// Variable-length integer reader: if the upper-nibble `len` token was the
6202/// max value (15), read additional 0xFF-terminated bytes from `src[*ip..]`
6203/// and accumulate. Returns `None` if input is truncated or the accumulator
6204/// would overflow.
6205///
6206/// Mirrors upstream `read_variable_length` (lz4.c:1975).
6207#[inline]
6208fn read_len(src: &[u8], ip: &mut usize, mut len: usize) -> Option<usize> {
6209    if len == 15 {
6210        loop {
6211            if *ip >= src.len() {
6212                return None;
6213            }
6214            let b = src[*ip] as usize;
6215            *ip += 1;
6216            len = len.checked_add(b)?;
6217            if b != 255 {
6218                break;
6219            }
6220        }
6221    }
6222    Some(len)
6223}
6224
6225/// Compute the hash table index for the bytes at `src[pos..]`. On 64-bit
6226/// platforms uses the 5-byte hash; on 32-bit uses the 4-byte hash. `by_u16`
6227/// requests the `byU16` table type (extra bit of hash, smaller table).
6228///
6229/// Mirrors upstream `LZ4_hashPosition` (lz4.c:793).
6230fn hash_fast(src: &[u8], pos: usize, by_u16: bool) -> usize {
6231    if by_u16 {
6232        hash4_bits(src, pos, LZ4_HASH_BITS_U16)
6233    } else if usize::BITS == 64 {
6234        let v = read_u64(&src[pos..]);
6235        (((v << 24).wrapping_mul(889_523_592_379)) >> (64 - LZ4_HASH_BITS)) as usize
6236    } else {
6237        hash4_bits(src, pos, LZ4_HASH_BITS)
6238    }
6239}
6240
6241#[inline(always)]
6242fn hash_fast_const<const BY_U16: bool>(src: &[u8], pos: usize) -> usize {
6243    if BY_U16 {
6244        hash4_bits(src, pos, LZ4_HASH_BITS_U16)
6245    } else if usize::BITS == 64 {
6246        let v = read_u64(&src[pos..]);
6247        (((v << 24).wrapping_mul(889_523_592_379)) >> (64 - LZ4_HASH_BITS)) as usize
6248    } else {
6249        hash4_bits(src, pos, LZ4_HASH_BITS)
6250    }
6251}
6252
6253/// Knuth-multiplicative hash of the 4-byte window at `src[pos..]` into a
6254/// `bits`-bit table index.
6255///
6256/// Mirrors upstream `LZ4_hash4` (lz4.c:773).
6257fn hash4_bits(src: &[u8], pos: usize, bits: usize) -> usize {
6258    let v = read_u32(&src[pos..]);
6259    ((v.wrapping_mul(2_654_435_761)) >> (32 - bits)) as usize
6260}
6261
6262/// 4-byte hash sized for the LZ4HC match-finder table.
6263///
6264/// Mirrors upstream `LZ4HC_hashPtr` (lz4hc.c) — same `2_654_435_761`
6265/// multiplier as `LZ4_hash4` but shifted to `LZ4HC_HASH_BITS`.
6266fn hash4_hc(src: &[u8], pos: usize) -> usize {
6267    let v = read_u32(&src[pos..]);
6268    ((v.wrapping_mul(2_654_435_761)) >> (32 - LZ4HC_HASH_BITS)) as usize
6269}
6270
6271/// 4-byte hash sized for the LZ4MID (medium-compression) hash table.
6272fn hash4_mid(src: &[u8], pos: usize) -> usize {
6273    hash4_bits(src, pos, LZ4MID_HASH_BITS)
6274}
6275
6276/// 8-byte hash sized for the LZ4MID secondary table, using a 64-bit prime
6277/// multiplier on the upper bits of the window.
6278///
6279/// Mirrors upstream `LZ4MID_hash8` (lz4.c).
6280fn hash8_mid(src: &[u8], pos: usize) -> usize {
6281    let v = read_u64(&src[pos..]);
6282    (((v << 8).wrapping_mul(58_295_818_150_454_627)) >> (64 - LZ4MID_HASH_BITS)) as usize
6283}
6284
6285/// Convert a caller-supplied `LZ4F_preferences_t` C struct into our internal
6286/// `FramePrefs`. A null pointer yields default preferences (matches upstream
6287/// behavior, which accepts `NULL` as "use defaults").
6288fn preferences_from_ptr(ptr: *const LZ4FPreferences) -> FramePrefs {
6289    if ptr.is_null() {
6290        return FramePrefs::default();
6291    }
6292    unsafe {
6293        let prefs = &*ptr;
6294        let id = match prefs.frame_info.block_size_id {
6295            BlockSize::Default | BlockSize::Max64KB => 4,
6296            BlockSize::Max256KB => 5,
6297            BlockSize::Max1MB => 6,
6298            BlockSize::Max4MB => 7,
6299        };
6300        FramePrefs {
6301            block_size_id: id,
6302            block_independent: matches!(prefs.frame_info.block_mode, BlockMode::Independent),
6303            block_checksum: matches!(
6304                prefs.frame_info.block_checksum_flag,
6305                BlockChecksum::BlockChecksumEnabled
6306            ),
6307            content_checksum: matches!(
6308                prefs.frame_info.content_checksum_flag,
6309                ContentChecksum::ChecksumEnabled
6310            ),
6311            content_size: prefs.frame_info.content_size,
6312            dict_id: prefs.frame_info.dict_id,
6313            compression_level: prefs.compression_level as c_int,
6314            auto_flush: prefs.auto_flush != 0,
6315            favor_dec_speed: prefs.favor_dec_speed != 0,
6316        }
6317    }
6318}
6319
6320/// Serialize the LZ4 frame header (magic + FLG/BD + optional content size +
6321/// optional dictID + header checksum byte) for the given `prefs`. The output
6322/// is between 7 and 19 bytes.
6323///
6324/// Mirrors the header-serialization portion of upstream `LZ4F_compressBegin`
6325/// (lz4frame.c).
6326fn frame_header(prefs: FramePrefs) -> Vec<u8> {
6327    let mut out = Vec::with_capacity(19);
6328    out.extend_from_slice(&LZ4F_MAGIC);
6329    let mut flg = 0x40;
6330    if prefs.block_independent {
6331        flg |= 0x20;
6332    }
6333    if prefs.block_checksum {
6334        flg |= 0x10;
6335    }
6336    if prefs.content_size != 0 {
6337        flg |= 0x08;
6338    }
6339    if prefs.content_checksum {
6340        flg |= 0x04;
6341    }
6342    if prefs.dict_id != 0 {
6343        flg |= 0x01;
6344    }
6345    out.push(flg);
6346    out.push(prefs.block_size_id << 4);
6347    if prefs.content_size != 0 {
6348        out.extend_from_slice(&prefs.content_size.to_le_bytes());
6349    }
6350    if prefs.dict_id != 0 {
6351        out.extend_from_slice(&prefs.dict_id.to_le_bytes());
6352    }
6353    let hc = (xxhash32(&out[4..], 0) >> 8) as u8;
6354    out.push(hc);
6355    out
6356}
6357
6358/// Parse an LZ4 frame header. On success returns the decoded preferences and
6359/// the number of header bytes consumed. Returns an `LZ4F_errorCodes` value on
6360/// failure (bad magic, reserved flag set, invalid block size id, bad header
6361/// checksum, etc.).
6362///
6363/// Mirrors upstream `LZ4F_decodeHeader` (lz4frame.c).
6364fn parse_frame_header(src: &[u8]) -> Result<(FramePrefs, usize), usize> {
6365    if src.len() < 7 || src[..4] != LZ4F_MAGIC {
6366        return if src.len() >= 4 && src[..4] != LZ4F_MAGIC {
6367            Err(ERROR_FRAME_TYPE_UNKNOWN)
6368        } else {
6369            Err(ERROR_BAD_HEADER)
6370        };
6371    }
6372    let flg = src[4];
6373    if flg & 0xC0 != 0x40 {
6374        return Err(ERROR_HEADER_VERSION_WRONG);
6375    }
6376    if flg & 0x02 != 0 {
6377        return Err(ERROR_RESERVED_FLAG_SET);
6378    }
6379    let bd = src[5];
6380    let block_size_id = (bd >> 4) & 0x07;
6381    if bd & 0x8F != 0 {
6382        return Err(ERROR_RESERVED_FLAG_SET);
6383    }
6384    if !(4..=7).contains(&block_size_id) {
6385        return Err(ERROR_MAX_BLOCK_SIZE_INVALID);
6386    }
6387    let mut pos = 6;
6388    let mut content_size = 0u64;
6389    let mut dict_id = 0u32;
6390    if flg & 0x08 != 0 {
6391        if src.len() < pos + 8 + 1 {
6392            return Err(ERROR_BAD_HEADER);
6393        }
6394        content_size =
6395            u64::from_le_bytes(src[pos..pos + 8].try_into().map_err(|_| ERROR_BAD_HEADER)?);
6396        pos += 8;
6397    }
6398    if flg & 0x01 != 0 {
6399        if src.len() < pos + 4 + 1 {
6400            return Err(ERROR_BAD_HEADER);
6401        }
6402        dict_id = u32::from_le_bytes(src[pos..pos + 4].try_into().map_err(|_| ERROR_BAD_HEADER)?);
6403        pos += 4;
6404    }
6405    if src.len() < pos + 1 {
6406        return Err(ERROR_BAD_HEADER);
6407    }
6408    let expected_hc = (xxhash32(&src[4..pos], 0) >> 8) as u8;
6409    if src[pos] != expected_hc {
6410        return Err(ERROR_HEADER_CHECKSUM_INVALID);
6411    }
6412    pos += 1;
6413    Ok((
6414        FramePrefs {
6415            block_size_id,
6416            block_independent: flg & 0x20 != 0,
6417            block_checksum: flg & 0x10 != 0,
6418            content_checksum: flg & 0x04 != 0,
6419            content_size,
6420            dict_id,
6421            compression_level: 0,
6422            auto_flush: false,
6423            favor_dec_speed: false,
6424        },
6425        pos,
6426    ))
6427}
6428
6429/// Returns true if the first 4 bytes of `src` are a skippable-frame magic
6430/// number (`0x184D2A50` .. `0x184D2A5F`).
6431///
6432/// Mirrors the skippable-frame detection in upstream `LZ4F_decompress`
6433/// (lz4frame.c).
6434fn is_skippable_magic_prefix(src: &[u8]) -> bool {
6435    if src.len() < 4 {
6436        return false;
6437    }
6438    let magic = u32::from_le_bytes(src[..4].try_into().unwrap());
6439    (LZ4F_SKIPPABLE_MAGIC_MIN..=LZ4F_SKIPPABLE_MAGIC_MAX).contains(&magic)
6440}
6441
6442/// Total byte length (header + payload) of the skippable frame that starts
6443/// at `src`. Returns `None` if `src` does not begin with a skippable magic
6444/// or is too short to read the 32-bit length field.
6445fn parse_skippable_frame_len(src: &[u8]) -> Option<usize> {
6446    if src.len() < 8 || !is_skippable_magic_prefix(src) {
6447        return None;
6448    }
6449    let content_len = u32::from_le_bytes(src[4..8].try_into().ok()?) as usize;
6450    content_len.checked_add(8)
6451}
6452
6453/// Try to consume a frame (or skippable) header from `ctx.input`. Returns
6454/// `Ok(true)` if a header was parsed (and `ctx.parsed_header` updated) or
6455/// the frame was a complete skippable, `Ok(false)` if more input is needed,
6456/// and an error code on malformed input.
6457///
6458/// Mirrors the header-parsing stage of upstream `LZ4F_decompress`
6459/// (lz4frame.c, `dstage_getFrameHeader` / `dstage_storeFrameHeader`).
6460fn parse_frame_header_if_available(ctx: &mut DecompressionCtx) -> Result<bool, usize> {
6461    if ctx.done || ctx.parsed_header {
6462        return Ok(true);
6463    }
6464    if ctx.input.len().saturating_sub(ctx.pos) >= 4
6465        && is_skippable_magic_prefix(&ctx.input[ctx.pos..])
6466    {
6467        if ctx.input.len().saturating_sub(ctx.pos) < 8 {
6468            return Ok(false);
6469        }
6470        let Some(skip_len) = parse_skippable_frame_len(&ctx.input[ctx.pos..]) else {
6471            return Err(ERROR_BAD_HEADER);
6472        };
6473        if ctx.input.len().saturating_sub(ctx.pos) < skip_len {
6474            compact_input(ctx);
6475            return Ok(false);
6476        }
6477        ctx.pos += skip_len;
6478        ctx.done = true;
6479        compact_input(ctx);
6480        return Ok(true);
6481    }
6482    if ctx.input.len().saturating_sub(ctx.pos) < 7 {
6483        return Ok(false);
6484    }
6485    if let Some(header_len) = expected_frame_header_len(&ctx.input[ctx.pos..]) {
6486        if ctx.input.len().saturating_sub(ctx.pos) < header_len {
6487            return Ok(false);
6488        }
6489    }
6490    let (prefs, header_len) = parse_frame_header(&ctx.input[ctx.pos..])?;
6491    ctx.pos += header_len;
6492    apply_frame_prefs(ctx, prefs);
6493    Ok(true)
6494}
6495
6496/// Copy parsed frame preferences into the decompression context and reset
6497/// per-frame state (content checksum running total, block dictionary if no
6498/// external dictionary was set, etc.).
6499fn apply_frame_prefs(ctx: &mut DecompressionCtx, prefs: FramePrefs) {
6500    ctx.parsed_header = true;
6501    ctx.block_checksum = prefs.block_checksum;
6502    ctx.content_checksum = prefs.content_checksum;
6503    ctx.content_size = prefs.content_size;
6504    ctx.content_read = 0;
6505    ctx.dict_id = prefs.dict_id;
6506    ctx.block_independent = prefs.block_independent;
6507    ctx.block_max = block_max_size(prefs.block_size_id);
6508    if !ctx.external_dictionary {
6509        ctx.dictionary.clear();
6510    }
6511    ctx.external_dictionary = false;
6512    ctx.raw_block_remaining = 0;
6513    ctx.raw_block_checksum = XxHash32::new(0);
6514}
6515
6516fn start_raw_block(ctx: &mut DecompressionCtx, block_len: usize) {
6517    ctx.raw_block_remaining = block_len;
6518    ctx.raw_block_checksum = XxHash32::new(0);
6519}
6520
6521macro_rules! dispatch_frame_block_flags {
6522    ($ctx:expr, $skip_checksums:expr, $func:ident($($arg:expr),* $(,)?)) => {
6523        match (
6524            $ctx.block_checksum,
6525            $ctx.content_checksum,
6526            $ctx.block_independent,
6527            $skip_checksums,
6528        ) {
6529            (false, false, false, false) => $func::<false, false, false, false>($($arg),*),
6530            (false, false, false, true) => $func::<false, false, false, true>($($arg),*),
6531            (false, false, true, false) => $func::<false, false, true, false>($($arg),*),
6532            (false, false, true, true) => $func::<false, false, true, true>($($arg),*),
6533            (false, true, false, false) => $func::<false, true, false, false>($($arg),*),
6534            (false, true, false, true) => $func::<false, true, false, true>($($arg),*),
6535            (false, true, true, false) => $func::<false, true, true, false>($($arg),*),
6536            (false, true, true, true) => $func::<false, true, true, true>($($arg),*),
6537            (true, false, false, false) => $func::<true, false, false, false>($($arg),*),
6538            (true, false, false, true) => $func::<true, false, false, true>($($arg),*),
6539            (true, false, true, false) => $func::<true, false, true, false>($($arg),*),
6540            (true, false, true, true) => $func::<true, false, true, true>($($arg),*),
6541            (true, true, false, false) => $func::<true, true, false, false>($($arg),*),
6542            (true, true, false, true) => $func::<true, true, false, true>($($arg),*),
6543            (true, true, true, false) => $func::<true, true, true, false>($($arg),*),
6544            (true, true, true, true) => $func::<true, true, true, true>($($arg),*),
6545        }
6546    };
6547}
6548
6549fn update_raw_block_output<
6550    const BLOCK_CHECKSUM: bool,
6551    const CONTENT_CHECKSUM: bool,
6552    const BLOCK_INDEPENDENT: bool,
6553    const SKIP_CHECKSUMS: bool,
6554>(
6555    ctx: &mut DecompressionCtx,
6556    bytes: &[u8],
6557) {
6558    if BLOCK_CHECKSUM && !SKIP_CHECKSUMS {
6559        ctx.raw_block_checksum.update(bytes);
6560    }
6561    if CONTENT_CHECKSUM {
6562        ctx.content_hasher.update(bytes);
6563    }
6564    ctx.content_read += bytes.len() as u64;
6565    if BLOCK_INDEPENDENT {
6566        ctx.dictionary.clear();
6567    } else {
6568        append_hc_dictionary(&mut ctx.dictionary, bytes);
6569    }
6570}
6571
6572fn validate_content_size_after_block(ctx: &DecompressionCtx) -> Result<(), usize> {
6573    if ctx.content_size != 0 && ctx.content_read > ctx.content_size {
6574        Err(ERROR_FRAME_SIZE_WRONG)
6575    } else {
6576        Ok(())
6577    }
6578}
6579
6580fn consume_frame_end_from_slice<
6581    const BLOCK_CHECKSUM: bool,
6582    const CONTENT_CHECKSUM: bool,
6583    const BLOCK_INDEPENDENT: bool,
6584    const SKIP_CHECKSUMS: bool,
6585>(
6586    ctx: &mut DecompressionCtx,
6587    src: &[u8],
6588    consumed: &mut usize,
6589) -> Option<Result<(), usize>> {
6590    if src.len().saturating_sub(*consumed) < 4 {
6591        return None;
6592    }
6593    let end_mark = u32::from_le_bytes(src[*consumed..*consumed + 4].try_into().unwrap());
6594    if end_mark != 0 {
6595        return Some(Ok(()));
6596    }
6597    let trailer = if CONTENT_CHECKSUM { 4 } else { 0 };
6598    if src.len().saturating_sub(*consumed) < 4 + trailer {
6599        return None;
6600    }
6601    *consumed += 4;
6602    if CONTENT_CHECKSUM {
6603        let stored = u32::from_le_bytes(src[*consumed..*consumed + 4].try_into().unwrap());
6604        *consumed += 4;
6605        if !SKIP_CHECKSUMS && stored != ctx.content_hasher.digest() {
6606            return Some(Err(ERROR_CHECKSUM_INVALID));
6607        }
6608    }
6609    if ctx.content_size != 0 && ctx.content_read != ctx.content_size {
6610        return Some(Err(ERROR_FRAME_SIZE_WRONG));
6611    }
6612    ctx.done = true;
6613    Some(Ok(()))
6614}
6615
6616fn consume_frame_end_from_input<
6617    const BLOCK_CHECKSUM: bool,
6618    const CONTENT_CHECKSUM: bool,
6619    const BLOCK_INDEPENDENT: bool,
6620    const SKIP_CHECKSUMS: bool,
6621>(
6622    ctx: &mut DecompressionCtx,
6623) -> Option<Result<(), usize>> {
6624    if ctx.input.len().saturating_sub(ctx.pos) < 4 {
6625        return None;
6626    }
6627    let end_mark = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6628    if end_mark != 0 {
6629        return Some(Ok(()));
6630    }
6631    let trailer = if CONTENT_CHECKSUM { 4 } else { 0 };
6632    if ctx.input.len().saturating_sub(ctx.pos) < 4 + trailer {
6633        return None;
6634    }
6635    ctx.pos += 4;
6636    if CONTENT_CHECKSUM {
6637        let stored = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6638        ctx.pos += 4;
6639        if !SKIP_CHECKSUMS && stored != ctx.content_hasher.digest() {
6640            return Some(Err(ERROR_CHECKSUM_INVALID));
6641        }
6642    }
6643    if ctx.content_size != 0 && ctx.content_read != ctx.content_size {
6644        return Some(Err(ERROR_FRAME_SIZE_WRONG));
6645    }
6646    ctx.done = true;
6647    Some(Ok(()))
6648}
6649
6650fn finish_raw_block_checksum_from_slice<
6651    const BLOCK_CHECKSUM: bool,
6652    const CONTENT_CHECKSUM: bool,
6653    const BLOCK_INDEPENDENT: bool,
6654    const SKIP_CHECKSUMS: bool,
6655>(
6656    ctx: &mut DecompressionCtx,
6657    src: &[u8],
6658    consumed: &mut usize,
6659) -> Option<Result<(), usize>> {
6660    if ctx.raw_block_remaining != 0 || !BLOCK_CHECKSUM {
6661        return Some(Ok(()));
6662    }
6663    if src.len().saturating_sub(*consumed) < 4 {
6664        return None;
6665    }
6666    let stored = u32::from_le_bytes(src[*consumed..*consumed + 4].try_into().unwrap());
6667    *consumed += 4;
6668    if !SKIP_CHECKSUMS && stored != ctx.raw_block_checksum.digest() {
6669        return Some(Err(ERROR_BLOCK_CHECKSUM_INVALID));
6670    }
6671    Some(Ok(()))
6672}
6673
6674fn finish_raw_block_checksum_from_input<
6675    const BLOCK_CHECKSUM: bool,
6676    const CONTENT_CHECKSUM: bool,
6677    const BLOCK_INDEPENDENT: bool,
6678    const SKIP_CHECKSUMS: bool,
6679>(
6680    ctx: &mut DecompressionCtx,
6681) -> Option<Result<(), usize>> {
6682    if ctx.raw_block_remaining != 0 || !BLOCK_CHECKSUM {
6683        return Some(Ok(()));
6684    }
6685    if ctx.input.len().saturating_sub(ctx.pos) < 4 {
6686        compact_input(ctx);
6687        return None;
6688    }
6689    let stored = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6690    ctx.pos += 4;
6691    if !SKIP_CHECKSUMS && stored != ctx.raw_block_checksum.digest() {
6692        return Some(Err(ERROR_BLOCK_CHECKSUM_INVALID));
6693    }
6694    Some(Ok(()))
6695}
6696
6697fn try_copy_raw_block_slice_to_dst(
6698    ctx: &mut DecompressionCtx,
6699    src: &[u8],
6700    dst: &mut [u8],
6701    skip_checksums: bool,
6702) -> Option<Result<(usize, usize), usize>> {
6703    dispatch_frame_block_flags!(
6704        ctx,
6705        skip_checksums,
6706        try_copy_raw_block_slice_to_dst_spec(ctx, src, dst)
6707    )
6708}
6709
6710fn try_copy_raw_block_slice_to_dst_spec<
6711    const BLOCK_CHECKSUM: bool,
6712    const CONTENT_CHECKSUM: bool,
6713    const BLOCK_INDEPENDENT: bool,
6714    const SKIP_CHECKSUMS: bool,
6715>(
6716    ctx: &mut DecompressionCtx,
6717    src: &[u8],
6718    dst: &mut [u8],
6719) -> Option<Result<(usize, usize), usize>> {
6720    if ctx.raw_block_remaining == 0 {
6721        return None;
6722    }
6723    let to_copy = cmp::min(ctx.raw_block_remaining, cmp::min(src.len(), dst.len()));
6724    if to_copy > 0 {
6725        unsafe {
6726            ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), to_copy);
6727        }
6728        update_raw_block_output::<
6729            BLOCK_CHECKSUM,
6730            CONTENT_CHECKSUM,
6731            BLOCK_INDEPENDENT,
6732            SKIP_CHECKSUMS,
6733        >(ctx, &dst[..to_copy]);
6734        ctx.raw_block_remaining -= to_copy;
6735    }
6736    let mut consumed = to_copy;
6737    if ctx.raw_block_remaining == 0 {
6738        match finish_raw_block_checksum_from_slice::<
6739            BLOCK_CHECKSUM,
6740            CONTENT_CHECKSUM,
6741            BLOCK_INDEPENDENT,
6742            SKIP_CHECKSUMS,
6743        >(ctx, src, &mut consumed)
6744        {
6745            Some(Ok(())) => {}
6746            Some(Err(code)) => return Some(Err(code)),
6747            None => {}
6748        }
6749        if let Err(code) = validate_content_size_after_block(ctx) {
6750            return Some(Err(code));
6751        }
6752        match consume_frame_end_from_slice::<
6753            BLOCK_CHECKSUM,
6754            CONTENT_CHECKSUM,
6755            BLOCK_INDEPENDENT,
6756            SKIP_CHECKSUMS,
6757        >(ctx, src, &mut consumed)
6758        {
6759            Some(Ok(())) | None => {}
6760            Some(Err(code)) => return Some(Err(code)),
6761        }
6762    }
6763    Some(Ok((consumed, to_copy)))
6764}
6765
6766fn copy_raw_block_from_input_to_dst(
6767    ctx: &mut DecompressionCtx,
6768    dst: &mut [u8],
6769    skip_checksums: bool,
6770) -> Option<Result<usize, usize>> {
6771    dispatch_frame_block_flags!(
6772        ctx,
6773        skip_checksums,
6774        copy_raw_block_from_input_to_dst_spec(ctx, dst)
6775    )
6776}
6777
6778fn copy_raw_block_from_input_to_dst_spec<
6779    const BLOCK_CHECKSUM: bool,
6780    const CONTENT_CHECKSUM: bool,
6781    const BLOCK_INDEPENDENT: bool,
6782    const SKIP_CHECKSUMS: bool,
6783>(
6784    ctx: &mut DecompressionCtx,
6785    dst: &mut [u8],
6786) -> Option<Result<usize, usize>> {
6787    if ctx.raw_block_remaining == 0 {
6788        return None;
6789    }
6790    let available = ctx.input.len().saturating_sub(ctx.pos);
6791    let to_copy = cmp::min(ctx.raw_block_remaining, cmp::min(available, dst.len()));
6792    if to_copy > 0 {
6793        unsafe {
6794            ptr::copy_nonoverlapping(ctx.input.as_ptr().add(ctx.pos), dst.as_mut_ptr(), to_copy);
6795        }
6796        ctx.pos += to_copy;
6797        update_raw_block_output::<
6798            BLOCK_CHECKSUM,
6799            CONTENT_CHECKSUM,
6800            BLOCK_INDEPENDENT,
6801            SKIP_CHECKSUMS,
6802        >(ctx, &dst[..to_copy]);
6803        ctx.raw_block_remaining -= to_copy;
6804    }
6805    if ctx.raw_block_remaining == 0 {
6806        match finish_raw_block_checksum_from_input::<
6807            BLOCK_CHECKSUM,
6808            CONTENT_CHECKSUM,
6809            BLOCK_INDEPENDENT,
6810            SKIP_CHECKSUMS,
6811        >(ctx)
6812        {
6813            Some(Ok(())) => {}
6814            Some(Err(code)) => return Some(Err(code)),
6815            None => {}
6816        }
6817        if let Err(code) = validate_content_size_after_block(ctx) {
6818            return Some(Err(code));
6819        }
6820        match consume_frame_end_from_input::<
6821            BLOCK_CHECKSUM,
6822            CONTENT_CHECKSUM,
6823            BLOCK_INDEPENDENT,
6824            SKIP_CHECKSUMS,
6825        >(ctx)
6826        {
6827            Some(Ok(())) | None => {}
6828            Some(Err(code)) => return Some(Err(code)),
6829        }
6830    }
6831    compact_input(ctx);
6832    Some(Ok(to_copy))
6833}
6834
6835/// Reconstruct an `LZ4F_frameInfo_t` C struct from the current decompression
6836/// context state, for callers of `LZ4F_getFrameInfo`.
6837///
6838/// Mirrors upstream `LZ4F_getFrameInfo` (lz4frame.c).
6839fn frame_info_from_decompression_ctx(ctx: &DecompressionCtx) -> LZ4FFrameInfo {
6840    LZ4FFrameInfo {
6841        block_size_id: match ctx.block_max {
6842            n if n == 256 * 1024 => BlockSize::Max256KB,
6843            n if n == 1024 * 1024 => BlockSize::Max1MB,
6844            n if n == 4 * 1024 * 1024 => BlockSize::Max4MB,
6845            _ => BlockSize::Max64KB,
6846        },
6847        block_mode: if ctx.block_independent {
6848            BlockMode::Independent
6849        } else {
6850            BlockMode::Linked
6851        },
6852        content_checksum_flag: if ctx.content_checksum {
6853            ContentChecksum::ChecksumEnabled
6854        } else {
6855            ContentChecksum::NoChecksum
6856        },
6857        frame_type: FrameType::Frame,
6858        content_size: ctx.content_size,
6859        dict_id: ctx.dict_id,
6860        block_checksum_flag: if ctx.block_checksum {
6861            BlockChecksum::BlockChecksumEnabled
6862        } else {
6863            BlockChecksum::NoBlockChecksum
6864        },
6865    }
6866}
6867
6868/// Decode the next frame block from `ctx.input` directly into `dst`,
6869/// avoiding the staging `ctx.pending` buffer. Returns:
6870/// - `None` when more input is needed, when `dst` is too small to receive a
6871///   worst-case block, or when there is pending output not yet consumed.
6872/// - `Some(Ok(written))` on a successful block decode (or end-of-frame, with
6873///   `written = 0`).
6874/// - `Some(Err(code))` on malformed input.
6875///
6876/// Mirrors the in-place fast path of upstream `LZ4F_decompress` (lz4frame.c).
6877fn try_decompress_frame_block_to_dst(
6878    ctx: &mut DecompressionCtx,
6879    dst: &mut [u8],
6880    skip_checksums: bool,
6881) -> Option<Result<usize, usize>> {
6882    dispatch_frame_block_flags!(
6883        ctx,
6884        skip_checksums,
6885        try_decompress_frame_block_to_dst_spec(ctx, dst)
6886    )
6887}
6888
6889fn try_decompress_frame_block_to_dst_spec<
6890    const BLOCK_CHECKSUM: bool,
6891    const CONTENT_CHECKSUM: bool,
6892    const BLOCK_INDEPENDENT: bool,
6893    const SKIP_CHECKSUMS: bool,
6894>(
6895    ctx: &mut DecompressionCtx,
6896    dst: &mut [u8],
6897) -> Option<Result<usize, usize>> {
6898    if ctx.done || !ctx.parsed_header || !pending_is_empty(ctx) {
6899        return None;
6900    }
6901    if ctx.input.len().saturating_sub(ctx.pos) < 4 {
6902        compact_input(ctx);
6903        return None;
6904    }
6905    let block_header = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6906    let raw = block_header & 0x8000_0000 != 0;
6907    let block_len = (block_header & 0x7FFF_FFFF) as usize;
6908    if block_len == 0 {
6909        return None;
6910    }
6911    if block_len > ctx.block_max {
6912        return Some(Err(ERROR_MAX_BLOCK_SIZE_INVALID));
6913    }
6914    let checksum_len = if BLOCK_CHECKSUM { 4 } else { 0 };
6915    if raw {
6916        ctx.pos += 4;
6917        start_raw_block(ctx, block_len);
6918        return copy_raw_block_from_input_to_dst_spec::<
6919            BLOCK_CHECKSUM,
6920            CONTENT_CHECKSUM,
6921            BLOCK_INDEPENDENT,
6922            SKIP_CHECKSUMS,
6923        >(ctx, dst);
6924    }
6925    if ctx.input.len().saturating_sub(ctx.pos) < 4 + block_len + checksum_len {
6926        compact_input(ctx);
6927        return None;
6928    }
6929    if dst.len() < ctx.block_max {
6930        return None;
6931    }
6932
6933    ctx.pos += 4;
6934    let block_start = ctx.pos;
6935    let block_end = block_start + block_len;
6936    if BLOCK_CHECKSUM && !SKIP_CHECKSUMS {
6937        let stored = u32::from_le_bytes(
6938            ctx.input[block_end..block_end + checksum_len]
6939                .try_into()
6940                .unwrap(),
6941        );
6942        if stored != xxhash32(&ctx.input[block_start..block_end], 0) {
6943            return Some(Err(ERROR_BLOCK_CHECKSUM_INVALID));
6944        }
6945    }
6946
6947    let n = if BLOCK_INDEPENDENT && ctx.dictionary.is_empty() {
6948        decompress_block(&ctx.input[block_start..block_end], dst)
6949    } else {
6950        decompress_block_with_dict(&ctx.input[block_start..block_end], dst, &ctx.dictionary)
6951    };
6952    let written = match n {
6953        Some(n) => n,
6954        None => return Some(Err(ERROR_DECOMPRESSION_FAILED)),
6955    };
6956
6957    if CONTENT_CHECKSUM {
6958        ctx.content_hasher.update(&dst[..written]);
6959    }
6960    ctx.content_read += written as u64;
6961    if BLOCK_INDEPENDENT {
6962        ctx.dictionary.clear();
6963    } else {
6964        append_hc_dictionary(&mut ctx.dictionary, &dst[..written]);
6965    }
6966    ctx.pos = block_end + checksum_len;
6967    if ctx.content_size != 0 && ctx.content_read > ctx.content_size {
6968        return Some(Err(ERROR_FRAME_SIZE_WRONG));
6969    }
6970    if ctx.content_size != 0 {
6971        let trailer = if CONTENT_CHECKSUM { 4 } else { 0 };
6972        if ctx.input.len().saturating_sub(ctx.pos) >= 4 {
6973            let end_mark = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6974            if end_mark != 0 {
6975                if ctx.content_read >= ctx.content_size {
6976                    return Some(Err(ERROR_FRAME_SIZE_WRONG));
6977                }
6978            } else if ctx.content_read != ctx.content_size {
6979                return Some(Err(ERROR_FRAME_SIZE_WRONG));
6980            } else if ctx.input.len().saturating_sub(ctx.pos) >= 4 + trailer {
6981                ctx.pos += 4;
6982                if CONTENT_CHECKSUM {
6983                    let stored =
6984                        u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
6985                    if !SKIP_CHECKSUMS && stored != ctx.content_hasher.digest() {
6986                        return Some(Err(ERROR_CHECKSUM_INVALID));
6987                    }
6988                    ctx.pos += 4;
6989                }
6990                ctx.done = true;
6991            }
6992        }
6993    }
6994    compact_input(ctx);
6995    Some(Ok(written))
6996}
6997
6998/// Variant of `try_decompress_frame_block_to_dst` that reads a single block
6999/// from the caller-provided `src` slice (rather than from `ctx.input`),
7000/// returning `(input_consumed, output_written)` on success. Used when the
7001/// caller wants to drive decompression block-at-a-time without accumulating
7002/// input inside the context.
7003///
7004/// Mirrors the in-place block-decompression path of upstream `LZ4F_decompress`
7005/// (lz4frame.c).
7006fn try_decompress_frame_block_slice_to_dst(
7007    ctx: &mut DecompressionCtx,
7008    src: &[u8],
7009    dst: &mut [u8],
7010    skip_checksums: bool,
7011) -> Option<Result<(usize, usize), usize>> {
7012    dispatch_frame_block_flags!(
7013        ctx,
7014        skip_checksums,
7015        try_decompress_frame_block_slice_to_dst_spec(ctx, src, dst)
7016    )
7017}
7018
7019fn try_decompress_frame_block_slice_to_dst_spec<
7020    const BLOCK_CHECKSUM: bool,
7021    const CONTENT_CHECKSUM: bool,
7022    const BLOCK_INDEPENDENT: bool,
7023    const SKIP_CHECKSUMS: bool,
7024>(
7025    ctx: &mut DecompressionCtx,
7026    src: &[u8],
7027    dst: &mut [u8],
7028) -> Option<Result<(usize, usize), usize>> {
7029    if src.len() < 4 {
7030        return None;
7031    }
7032    let block_header = u32::from_le_bytes(src[..4].try_into().unwrap());
7033    let raw = block_header & 0x8000_0000 != 0;
7034    let block_len = (block_header & 0x7FFF_FFFF) as usize;
7035    if block_len == 0 {
7036        let trailer = if CONTENT_CHECKSUM { 4 } else { 0 };
7037        if src.len() < 4 + trailer {
7038            return None;
7039        }
7040        if CONTENT_CHECKSUM && !SKIP_CHECKSUMS {
7041            let stored = u32::from_le_bytes(src[4..8].try_into().unwrap());
7042            if stored != ctx.content_hasher.digest() {
7043                return Some(Err(ERROR_CHECKSUM_INVALID));
7044            }
7045        }
7046        if ctx.content_size != 0 && ctx.content_read != ctx.content_size {
7047            return Some(Err(ERROR_FRAME_SIZE_WRONG));
7048        }
7049        ctx.done = true;
7050        return Some(Ok((4 + trailer, 0)));
7051    }
7052    if block_len > ctx.block_max {
7053        return Some(Err(ERROR_MAX_BLOCK_SIZE_INVALID));
7054    }
7055
7056    let checksum_len = if BLOCK_CHECKSUM { 4 } else { 0 };
7057    if raw {
7058        start_raw_block(ctx, block_len);
7059        return try_copy_raw_block_slice_to_dst_spec::<
7060            BLOCK_CHECKSUM,
7061            CONTENT_CHECKSUM,
7062            BLOCK_INDEPENDENT,
7063            SKIP_CHECKSUMS,
7064        >(ctx, &src[4..], dst)
7065        .map(|result| result.map(|(consumed, written)| (consumed + 4, written)));
7066    }
7067    if src.len() < 4 + block_len + checksum_len {
7068        return None;
7069    }
7070    if dst.len() < ctx.block_max {
7071        return None;
7072    }
7073
7074    let block_start = 4;
7075    let block_end = block_start + block_len;
7076    if BLOCK_CHECKSUM && !SKIP_CHECKSUMS {
7077        let stored =
7078            u32::from_le_bytes(src[block_end..block_end + checksum_len].try_into().unwrap());
7079        if stored != xxhash32(&src[block_start..block_end], 0) {
7080            return Some(Err(ERROR_BLOCK_CHECKSUM_INVALID));
7081        }
7082    }
7083
7084    let n = if BLOCK_INDEPENDENT && ctx.dictionary.is_empty() {
7085        decompress_block(&src[block_start..block_end], dst)
7086    } else {
7087        decompress_block_with_dict(&src[block_start..block_end], dst, &ctx.dictionary)
7088    };
7089    let written = match n {
7090        Some(n) => n,
7091        None => return Some(Err(ERROR_DECOMPRESSION_FAILED)),
7092    };
7093
7094    if CONTENT_CHECKSUM {
7095        ctx.content_hasher.update(&dst[..written]);
7096    }
7097    ctx.content_read += written as u64;
7098    if BLOCK_INDEPENDENT {
7099        ctx.dictionary.clear();
7100    } else {
7101        append_hc_dictionary(&mut ctx.dictionary, &dst[..written]);
7102    }
7103    if ctx.content_size != 0 && ctx.content_read > ctx.content_size {
7104        return Some(Err(ERROR_FRAME_SIZE_WRONG));
7105    }
7106    Some(Ok((4 + block_len + checksum_len, written)))
7107}
7108
7109/// Drain as much of `ctx.input` as possible into `ctx.pending`, parsing the
7110/// frame header if not yet done and then decoding whole blocks until either
7111/// input runs out, output is produced (and must be consumed by the caller
7112/// before more is generated), or the end-of-frame marker is reached.
7113///
7114/// Mirrors the staged decompression loop of upstream `LZ4F_decompress`
7115/// (lz4frame.c).
7116fn parse_available_frame(ctx: &mut DecompressionCtx, skip_checksums: bool) -> Result<(), usize> {
7117    dispatch_frame_block_flags!(ctx, skip_checksums, parse_available_frame_spec(ctx))
7118}
7119
7120fn parse_available_frame_spec<
7121    const BLOCK_CHECKSUM: bool,
7122    const CONTENT_CHECKSUM: bool,
7123    const BLOCK_INDEPENDENT: bool,
7124    const SKIP_CHECKSUMS: bool,
7125>(
7126    ctx: &mut DecompressionCtx,
7127) -> Result<(), usize> {
7128    if ctx.done {
7129        return Ok(());
7130    }
7131    if !parse_frame_header_if_available(ctx)? || ctx.done {
7132        return Ok(());
7133    }
7134
7135    loop {
7136        if ctx.input.len().saturating_sub(ctx.pos) < 4 {
7137            compact_input(ctx);
7138            return Ok(());
7139        }
7140        let block_header = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
7141        let raw = block_header & 0x8000_0000 != 0;
7142        let block_len = (block_header & 0x7FFF_FFFF) as usize;
7143        if block_len == 0 {
7144            let trailer = if CONTENT_CHECKSUM { 4 } else { 0 };
7145            if ctx.input.len().saturating_sub(ctx.pos) < 4 + trailer {
7146                compact_input(ctx);
7147                return Ok(());
7148            }
7149            ctx.pos += 4;
7150            if CONTENT_CHECKSUM {
7151                let stored =
7152                    u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
7153                if !SKIP_CHECKSUMS && stored != ctx.content_hasher.digest() {
7154                    return Err(ERROR_CHECKSUM_INVALID);
7155                }
7156                ctx.pos += 4;
7157            }
7158            if ctx.content_size != 0 && ctx.content_read != ctx.content_size {
7159                return Err(ERROR_FRAME_SIZE_WRONG);
7160            }
7161            ctx.done = true;
7162            compact_input(ctx);
7163            return Ok(());
7164        }
7165        if block_len > ctx.block_max {
7166            return Err(ERROR_MAX_BLOCK_SIZE_INVALID);
7167        }
7168        let checksum_len = if BLOCK_CHECKSUM { 4 } else { 0 };
7169        ctx.pos += 4;
7170        let block_start = ctx.pos;
7171        if raw {
7172            start_raw_block(ctx, block_len);
7173            compact_input(ctx);
7174            return Ok(());
7175        }
7176        if ctx.input.len().saturating_sub(ctx.pos - 4) < 4 + block_len + checksum_len {
7177            ctx.pos -= 4;
7178            compact_input(ctx);
7179            return Ok(());
7180        }
7181        let block_end = block_start + block_len;
7182        if BLOCK_CHECKSUM && !SKIP_CHECKSUMS {
7183            let stored = u32::from_le_bytes(
7184                ctx.input[block_end..block_end + checksum_len]
7185                    .try_into()
7186                    .unwrap(),
7187            );
7188            if stored != xxhash32(&ctx.input[block_start..block_end], 0) {
7189                return Err(ERROR_BLOCK_CHECKSUM_INVALID);
7190            }
7191        }
7192        let mut out = vec![0u8; ctx.block_max];
7193        let n = if BLOCK_INDEPENDENT && ctx.dictionary.is_empty() {
7194            decompress_block(&ctx.input[block_start..block_end], &mut out)
7195        } else {
7196            decompress_block_with_dict(
7197                &ctx.input[block_start..block_end],
7198                &mut out,
7199                &ctx.dictionary,
7200            )
7201        }
7202        .ok_or(ERROR_DECOMPRESSION_FAILED)?;
7203        if CONTENT_CHECKSUM {
7204            ctx.content_hasher.update(&out[..n]);
7205        }
7206        ctx.content_read += n as u64;
7207        ctx.pending.extend_from_slice(&out[..n]);
7208        if BLOCK_INDEPENDENT {
7209            ctx.dictionary.clear();
7210        } else {
7211            append_hc_dictionary(&mut ctx.dictionary, &out[..n]);
7212        }
7213        ctx.pos = block_end + checksum_len;
7214        if ctx.content_size != 0 && ctx.content_read > ctx.content_size {
7215            return Err(ERROR_FRAME_SIZE_WRONG);
7216        }
7217        if !pending_is_empty(ctx) {
7218            compact_input(ctx);
7219            return Ok(());
7220        }
7221    }
7222}
7223
7224/// Number of decoded bytes still queued in `ctx.pending` waiting to be
7225/// handed back to the caller.
7226fn pending_len(ctx: &DecompressionCtx) -> usize {
7227    ctx.pending.len().saturating_sub(ctx.pending_pos)
7228}
7229
7230/// True if there is no decoded output queued in `ctx.pending`.
7231fn pending_is_empty(ctx: &DecompressionCtx) -> bool {
7232    pending_len(ctx) == 0
7233}
7234
7235/// Drop the already-consumed prefix of `ctx.input`, shifting any remaining
7236/// buffered bytes to the front so the buffer doesn't grow unboundedly across
7237/// streaming calls.
7238fn compact_input(ctx: &mut DecompressionCtx) {
7239    if ctx.pos > 0 {
7240        if ctx.pos >= ctx.input.len() {
7241            ctx.input.clear();
7242        } else {
7243            ctx.input.drain(..ctx.pos);
7244        }
7245        ctx.pos = 0;
7246    }
7247}
7248
7249/// Compute how many bytes of the current `LZ4F_decompress` call's input
7250/// have been consumed: all of it while still mid-frame, but only up to the
7251/// frame-end marker when `done` is set so callers can detect trailing data.
7252fn consumed_from_call(done: bool, src_size: usize, remaining_input_len: usize) -> usize {
7253    if !done {
7254        return src_size;
7255    }
7256    let remaining_from_call = cmp::min(remaining_input_len, src_size);
7257    src_size.saturating_sub(remaining_from_call)
7258}
7259
7260/// Given the first few bytes of a frame, compute the full header length
7261/// (7 base bytes plus 8 if content-size is present plus 4 if dictID is
7262/// present). Returns `None` if the magic doesn't match or the FLG byte is
7263/// not yet available.
7264fn expected_frame_header_len(src: &[u8]) -> Option<usize> {
7265    if src.len() < 5 || src[..4] != LZ4F_MAGIC {
7266        return None;
7267    }
7268    let flg = src[4];
7269    let mut len = 7;
7270    if flg & 0x08 != 0 {
7271        len += 8;
7272    }
7273    if flg & 0x01 != 0 {
7274        len += 4;
7275    }
7276    Some(len)
7277}
7278
7279/// The "size hint" returned by `LZ4F_decompress`: a lower bound on how much
7280/// more input the decoder needs to make progress (parse the rest of the
7281/// header, finish the current block, or read the next block header).
7282///
7283/// Mirrors the `nextSrcSizeHint` return value of upstream `LZ4F_decompress`
7284/// (lz4frame.c).
7285fn frame_hint(ctx: &DecompressionCtx) -> usize {
7286    if !ctx.parsed_header {
7287        let available = ctx.input.len().saturating_sub(ctx.pos);
7288        let expected = if available >= 5 {
7289            expected_frame_header_len(&ctx.input[ctx.pos..]).unwrap_or(7)
7290        } else {
7291            7
7292        };
7293        expected.saturating_sub(available)
7294    } else if ctx.raw_block_remaining > 0 {
7295        ctx.raw_block_remaining + if ctx.block_checksum { 4 } else { 0 } + 4
7296    } else if pending_is_empty(ctx) {
7297        let available = ctx.input.len().saturating_sub(ctx.pos);
7298        if available < 4 {
7299            return 4 - available;
7300        }
7301        let block_header = u32::from_le_bytes(ctx.input[ctx.pos..ctx.pos + 4].try_into().unwrap());
7302        let block_len = (block_header & 0x7FFF_FFFF) as usize;
7303        let needed = if block_len == 0 {
7304            4 + if ctx.content_checksum { 4 } else { 0 }
7305        } else {
7306            4 + block_len + if ctx.block_checksum { 4 } else { 0 }
7307        };
7308        cmp::max(needed.saturating_sub(available), 1)
7309    } else {
7310        1
7311    }
7312}
7313
7314fn decompression_free_status(ctx: &DecompressionCtx) -> usize {
7315    if ctx.done || (!ctx.parsed_header && ctx.input.is_empty() && pending_is_empty(ctx)) {
7316        return 0;
7317    }
7318    if !ctx.parsed_header {
7319        return if ctx.input.is_empty() { 0 } else { 1 };
7320    }
7321    if !pending_is_empty(ctx) {
7322        return 9;
7323    }
7324    if ctx.raw_block_remaining > 0 {
7325        return 5;
7326    }
7327    3
7328}
7329
7330/// Map the 4..=7 BD-byte block-size id to its `BlockSize` enum variant.
7331/// Unknown ids fall back to `Max64KB` to match upstream's permissive default.
7332fn block_size_enum(id: u8) -> BlockSize {
7333    match id {
7334        5 => BlockSize::Max256KB,
7335        6 => BlockSize::Max1MB,
7336        7 => BlockSize::Max4MB,
7337        _ => BlockSize::Max64KB,
7338    }
7339}
7340
7341/// Maximum uncompressed block size in bytes for a given BD-byte block-size
7342/// id (4 → 64 KiB, 5 → 256 KiB, 6 → 1 MiB, 7 → 4 MiB).
7343fn block_max_size(id: u8) -> usize {
7344    match id {
7345        5 => 256 * 1024,
7346        6 => 1024 * 1024,
7347        7 => 4 * 1024 * 1024,
7348        _ => 64 * 1024,
7349    }
7350}
7351
7352/// One-shot XXH32 hash of `input` with the given seed. Used for the frame
7353/// header checksum byte, the per-block checksum, and the content checksum.
7354///
7355/// Mirrors upstream `XXH32` (xxhash.c).
7356fn xxhash32(input: &[u8], seed: u32) -> u32 {
7357    let mut h = XxHash32::new(seed);
7358    h.update(input);
7359    h.digest()
7360}
7361
7362#[derive(Clone, Copy, Debug)]
7363struct XxHash32 {
7364    total: usize,
7365    seed: u32,
7366    v1: u32,
7367    v2: u32,
7368    v3: u32,
7369    v4: u32,
7370    mem: [u8; 16],
7371    mem_len: usize,
7372}
7373
7374impl XxHash32 {
7375    fn new(seed: u32) -> Self {
7376        Self {
7377            total: 0,
7378            seed,
7379            v1: seed.wrapping_add(0x9E37_79B1).wrapping_add(0x85EB_CA77),
7380            v2: seed.wrapping_add(0x85EB_CA77),
7381            v3: seed,
7382            v4: seed.wrapping_sub(0x9E37_79B1),
7383            mem: [0; 16],
7384            mem_len: 0,
7385        }
7386    }
7387
7388    fn update(&mut self, mut input: &[u8]) {
7389        self.total += input.len();
7390        if self.mem_len + input.len() < 16 {
7391            self.mem[self.mem_len..self.mem_len + input.len()].copy_from_slice(input);
7392            self.mem_len += input.len();
7393            return;
7394        }
7395        if self.mem_len > 0 {
7396            let fill = 16 - self.mem_len;
7397            self.mem[self.mem_len..16].copy_from_slice(&input[..fill]);
7398            let block = self.mem;
7399            self.process(&block);
7400            input = &input[fill..];
7401            self.mem_len = 0;
7402        }
7403        let mut v1 = self.v1;
7404        let mut v2 = self.v2;
7405        let mut v3 = self.v3;
7406        let mut v4 = self.v4;
7407        let mut p = input.as_ptr();
7408        let end = unsafe { p.add(input.len() & !15) };
7409        while p < end {
7410            v1 = round(v1, read_u32_ptr(p));
7411            v2 = round(v2, read_u32_ptr(unsafe { p.add(4) }));
7412            v3 = round(v3, read_u32_ptr(unsafe { p.add(8) }));
7413            v4 = round(v4, read_u32_ptr(unsafe { p.add(12) }));
7414            p = unsafe { p.add(16) };
7415        }
7416        self.v1 = v1;
7417        self.v2 = v2;
7418        self.v3 = v3;
7419        self.v4 = v4;
7420        let consumed = input.len() & !15;
7421        input = &input[consumed..];
7422        self.mem[..input.len()].copy_from_slice(input);
7423        self.mem_len = input.len();
7424    }
7425
7426    fn digest(&self) -> u32 {
7427        let mut h = if self.total >= 16 {
7428            self.v1
7429                .rotate_left(1)
7430                .wrapping_add(self.v2.rotate_left(7))
7431                .wrapping_add(self.v3.rotate_left(12))
7432                .wrapping_add(self.v4.rotate_left(18))
7433        } else {
7434            self.seed.wrapping_add(0x1656_67B1)
7435        };
7436        h = h.wrapping_add(self.total as u32);
7437        let mut p = &self.mem[..self.mem_len];
7438        while p.len() >= 4 {
7439            h = h
7440                .wrapping_add(read_u32(p).wrapping_mul(0xC2B2_AE3D))
7441                .rotate_left(17)
7442                .wrapping_mul(0x27D4_EB2F);
7443            p = &p[4..];
7444        }
7445        for &b in p {
7446            h = h
7447                .wrapping_add((b as u32).wrapping_mul(0x1656_67B1))
7448                .rotate_left(11)
7449                .wrapping_mul(0x9E37_79B1);
7450        }
7451        h ^= h >> 15;
7452        h = h.wrapping_mul(0x85EB_CA77);
7453        h ^= h >> 13;
7454        h = h.wrapping_mul(0xC2B2_AE3D);
7455        h ^ (h >> 16)
7456    }
7457
7458    fn process(&mut self, block: &[u8]) {
7459        self.v1 = round(self.v1, read_u32(&block[0..]));
7460        self.v2 = round(self.v2, read_u32(&block[4..]));
7461        self.v3 = round(self.v3, read_u32(&block[8..]));
7462        self.v4 = round(self.v4, read_u32(&block[12..]));
7463    }
7464}
7465
7466/// XXH32 mixing round: `acc = ROTL((acc + input*PRIME32_2), 13) * PRIME32_1`.
7467///
7468/// Mirrors upstream `XXH32_round` (xxhash.c:269).
7469fn round(acc: u32, input: u32) -> u32 {
7470    acc.wrapping_add(input.wrapping_mul(0x85EB_CA77))
7471        .rotate_left(13)
7472        .wrapping_mul(0x9E37_79B1)
7473}
7474
7475/// Little-endian unaligned 32-bit load from the start of `input`.
7476///
7477/// Mirrors upstream `LZ4_read32` / `LZ4_readLE32` (lz4.c:380).
7478#[inline]
7479fn read_u32(input: &[u8]) -> u32 {
7480    unsafe { ptr::read_unaligned(input.as_ptr() as *const u32).to_le() }
7481}
7482
7483/// Little-endian unaligned 16-bit load from the start of `input`.
7484///
7485/// Mirrors upstream `LZ4_read16` / `LZ4_readLE16` (lz4.c:379).
7486#[inline]
7487fn read_u16(input: &[u8]) -> u16 {
7488    unsafe { ptr::read_unaligned(input.as_ptr() as *const u16).to_le() }
7489}
7490
7491/// Pointer-based variant of `read_u16` for callers operating on raw
7492/// `*const u8` (no slice length to check).
7493#[inline]
7494fn read_u16_ptr(input: *const u8) -> u16 {
7495    unsafe { ptr::read_unaligned(input as *const u16).to_le() }
7496}
7497
7498/// Pointer-based variant of `read_u32` for callers operating on raw
7499/// `*const u8` (no slice length to check).
7500#[inline]
7501fn read_u32_ptr(input: *const u8) -> u32 {
7502    unsafe { ptr::read_unaligned(input as *const u32).to_le() }
7503}
7504
7505/// Little-endian unaligned 64-bit load from the start of `input`.
7506///
7507/// Mirrors upstream `LZ4_read_ARCH` (lz4.c:381) on 64-bit targets.
7508#[inline]
7509fn read_u64(input: &[u8]) -> u64 {
7510    unsafe { ptr::read_unaligned(input.as_ptr() as *const u64).to_le() }
7511}
7512
7513/// Pointer-based variant of `read_u64` for callers operating on raw
7514/// `*const u8` (no slice length to check).
7515#[inline]
7516fn read_u64_ptr(input: *const u8) -> u64 {
7517    unsafe { ptr::read_unaligned(input as *const u64).to_le() }
7518}
7519
7520#[cfg(test)]
7521mod tests {
7522    use super::*;
7523
7524    #[test]
7525    fn frame_error_code_mapping_matches_upstream_numbers() {
7526        unsafe {
7527            assert_eq!(LZ4F_isError(0), 0);
7528            assert_eq!(LZ4F_getErrorCode(0), 0);
7529            assert_eq!(
7530                std::ffi::CStr::from_ptr(LZ4F_getErrorName(0)).to_bytes(),
7531                b"Unspecified error code"
7532            );
7533
7534            let cases = [
7535                (ERROR_GENERIC, 1, b"ERROR_GENERIC".as_slice()),
7536                (
7537                    ERROR_MAX_BLOCK_SIZE_INVALID,
7538                    2,
7539                    b"ERROR_maxBlockSize_invalid".as_slice(),
7540                ),
7541                (
7542                    ERROR_BLOCK_MODE_INVALID,
7543                    3,
7544                    b"ERROR_blockMode_invalid".as_slice(),
7545                ),
7546                (
7547                    ERROR_PARAMETER_INVALID,
7548                    4,
7549                    b"ERROR_parameter_invalid".as_slice(),
7550                ),
7551                (
7552                    ERROR_COMPRESSION_LEVEL_INVALID,
7553                    5,
7554                    b"ERROR_compressionLevel_invalid".as_slice(),
7555                ),
7556                (
7557                    ERROR_HEADER_VERSION_WRONG,
7558                    6,
7559                    b"ERROR_headerVersion_wrong".as_slice(),
7560                ),
7561                (
7562                    ERROR_BLOCK_CHECKSUM_INVALID,
7563                    7,
7564                    b"ERROR_blockChecksum_invalid".as_slice(),
7565                ),
7566                (
7567                    ERROR_RESERVED_FLAG_SET,
7568                    8,
7569                    b"ERROR_reservedFlag_set".as_slice(),
7570                ),
7571                (
7572                    ERROR_ALLOCATION_FAILED,
7573                    9,
7574                    b"ERROR_allocation_failed".as_slice(),
7575                ),
7576                (
7577                    ERROR_SRC_SIZE_TOO_LARGE,
7578                    10,
7579                    b"ERROR_srcSize_tooLarge".as_slice(),
7580                ),
7581                (
7582                    ERROR_DST_TOO_SMALL,
7583                    11,
7584                    b"ERROR_dstMaxSize_tooSmall".as_slice(),
7585                ),
7586                (
7587                    ERROR_BAD_HEADER,
7588                    12,
7589                    b"ERROR_frameHeader_incomplete".as_slice(),
7590                ),
7591                (
7592                    ERROR_FRAME_SIZE_WRONG,
7593                    14,
7594                    b"ERROR_frameSize_wrong".as_slice(),
7595                ),
7596                (
7597                    ERROR_FRAME_TYPE_UNKNOWN,
7598                    13,
7599                    b"ERROR_frameType_unknown".as_slice(),
7600                ),
7601                (ERROR_SRC_PTR_WRONG, 15, b"ERROR_srcPtr_wrong".as_slice()),
7602                (
7603                    ERROR_DECOMPRESSION_FAILED,
7604                    16,
7605                    b"ERROR_decompressionFailed".as_slice(),
7606                ),
7607                (
7608                    ERROR_HEADER_CHECKSUM_INVALID,
7609                    17,
7610                    b"ERROR_headerChecksum_invalid".as_slice(),
7611                ),
7612                (
7613                    ERROR_CHECKSUM_INVALID,
7614                    18,
7615                    b"ERROR_contentChecksum_invalid".as_slice(),
7616                ),
7617                (
7618                    ERROR_FRAME_DECODING_ALREADY_STARTED,
7619                    19,
7620                    b"ERROR_frameDecoding_alreadyStarted".as_slice(),
7621                ),
7622                (
7623                    ERROR_COMPRESSION_STATE_UNINITIALIZED,
7624                    20,
7625                    b"ERROR_compressionState_uninitialized".as_slice(),
7626                ),
7627                (ERROR_PARAMETER_NULL, 21, b"ERROR_parameter_null".as_slice()),
7628                (ERROR_IO_WRITE, 22, b"ERROR_io_write".as_slice()),
7629                (ERROR_IO_READ, 23, b"ERROR_io_read".as_slice()),
7630            ];
7631            for (value, code, name) in cases {
7632                assert_eq!(LZ4F_isError(value), 1);
7633                assert_eq!(LZ4F_getErrorCode(value), code);
7634                assert_eq!(
7635                    std::ffi::CStr::from_ptr(LZ4F_getErrorName(value)).to_bytes(),
7636                    name
7637                );
7638            }
7639        }
7640    }
7641
7642    #[test]
7643    fn frame_default_block_size_id_maps_to_64kb() {
7644        assert_eq!(LZ4F_getBlockSize(0), 64 * 1024);
7645        assert_eq!(LZ4F_getBlockSize(4), 64 * 1024);
7646        assert_eq!(LZ4F_getBlockSize(3), ERROR_MAX_BLOCK_SIZE_INVALID);
7647    }
7648
7649    #[test]
7650    fn frame_compression_state_errors_match_upstream() {
7651        unsafe {
7652            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
7653            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
7654            let input = b"not started";
7655            let mut output = vec![0u8; 128];
7656
7657            assert_eq!(
7658                LZ4F_compressUpdate(
7659                    cctx,
7660                    output.as_mut_ptr(),
7661                    output.len(),
7662                    input.as_ptr(),
7663                    input.len(),
7664                    ptr::null(),
7665                ),
7666                ERROR_COMPRESSION_STATE_UNINITIALIZED
7667            );
7668            assert_eq!(
7669                LZ4F_uncompressedUpdate(
7670                    cctx,
7671                    output.as_mut_ptr() as *mut c_void,
7672                    output.len(),
7673                    input.as_ptr() as *const c_void,
7674                    input.len(),
7675                    ptr::null(),
7676                ),
7677                ERROR_COMPRESSION_STATE_UNINITIALIZED
7678            );
7679            assert_eq!(
7680                LZ4F_flush(cctx, output.as_mut_ptr(), output.len(), ptr::null()),
7681                ERROR_COMPRESSION_STATE_UNINITIALIZED
7682            );
7683            assert_eq!(
7684                LZ4F_compressEnd(cctx, output.as_mut_ptr(), output.len(), ptr::null()),
7685                ERROR_COMPRESSION_STATE_UNINITIALIZED
7686            );
7687
7688            let header_len =
7689                LZ4F_compressBegin(cctx, output.as_mut_ptr(), output.len(), ptr::null());
7690            assert_eq!(LZ4F_isError(header_len), 0);
7691            assert_eq!(
7692                LZ4F_compressEnd(
7693                    cctx,
7694                    output.as_mut_ptr().add(header_len),
7695                    output.len() - header_len,
7696                    ptr::null(),
7697                ),
7698                4
7699            );
7700            assert_eq!(
7701                LZ4F_compressEnd(cctx, output.as_mut_ptr(), output.len(), ptr::null()),
7702                ERROR_COMPRESSION_STATE_UNINITIALIZED
7703            );
7704            LZ4F_freeCompressionContext(cctx);
7705        }
7706    }
7707
7708    #[test]
7709    fn block_round_trip_repeated() {
7710        let input = b"Some data Some data Some data Some data";
7711        let mut compressed = vec![0u8; unsafe { LZ4_compressBound(input.len() as c_int) } as usize];
7712        let clen = unsafe {
7713            LZ4_compress_default(
7714                input.as_ptr() as *const c_char,
7715                compressed.as_mut_ptr() as *mut c_char,
7716                input.len() as c_int,
7717                compressed.len() as c_int,
7718            )
7719        };
7720        assert!(clen > 0);
7721        let mut output = vec![0u8; input.len()];
7722        let olen = unsafe {
7723            LZ4_decompress_safe(
7724                compressed.as_ptr() as *const c_char,
7725                output.as_mut_ptr() as *mut c_char,
7726                clen,
7727                output.len() as c_int,
7728            )
7729        };
7730        assert_eq!(olen as usize, input.len());
7731        assert_eq!(&output, input);
7732    }
7733
7734    #[test]
7735    fn block_fast_accepts_null_empty_source_and_rejects_oversize() {
7736        unsafe {
7737            let mut dst = [0u8; 16];
7738            let empty_len = LZ4_compress_fast(
7739                ptr::null(),
7740                dst.as_mut_ptr() as *mut c_char,
7741                0,
7742                dst.len() as c_int,
7743                1,
7744            );
7745            assert_eq!(empty_len, 1);
7746            assert_eq!(dst[0], 0);
7747
7748            assert_eq!(
7749                LZ4_compress_fast(
7750                    ptr::null(),
7751                    dst.as_mut_ptr() as *mut c_char,
7752                    1,
7753                    dst.len() as c_int,
7754                    1,
7755                ),
7756                0
7757            );
7758
7759            let src = [0u8; 1];
7760            assert_eq!(
7761                LZ4_compress_fast(
7762                    src.as_ptr() as *const c_char,
7763                    dst.as_mut_ptr() as *mut c_char,
7764                    LZ4_MAX_INPUT_SIZE + 1,
7765                    dst.len() as c_int,
7766                    1,
7767                ),
7768                0
7769            );
7770        }
7771    }
7772
7773    #[test]
7774    fn acceleration_is_clamped_to_upstream_range() {
7775        assert_eq!(normalize_acceleration(c_int::MIN), 1);
7776        assert_eq!(normalize_acceleration(0), 1);
7777        assert_eq!(normalize_acceleration(4), 4);
7778        assert_eq!(normalize_acceleration(65_537), 65_537);
7779        assert_eq!(normalize_acceleration(65_538), 65_537);
7780        assert_eq!(normalize_acceleration(c_int::MAX), 65_537);
7781    }
7782
7783    #[test]
7784    fn block_dest_size_and_ext_state_round_trip() {
7785        let input = b"dest-size-data-".repeat(2048);
7786        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
7787        let mut state = vec![0u8; LZ4_sizeofState() as usize];
7788        let mut compressed = vec![0u8; bound];
7789        let compressed_len = unsafe {
7790            LZ4_compress_fast_extState(
7791                state.as_mut_ptr() as *mut c_void,
7792                input.as_ptr() as *const c_char,
7793                compressed.as_mut_ptr() as *mut c_char,
7794                input.len() as c_int,
7795                compressed.len() as c_int,
7796                1,
7797            )
7798        };
7799        assert!(compressed_len > 0);
7800        assert!(state.iter().any(|&b| b != 0));
7801
7802        let mut output = vec![0u8; input.len()];
7803        let output_len = unsafe {
7804            LZ4_decompress_safe(
7805                compressed.as_ptr() as *const c_char,
7806                output.as_mut_ptr() as *mut c_char,
7807                compressed_len,
7808                output.len() as c_int,
7809            )
7810        };
7811        assert_eq!(output_len as usize, input.len());
7812        assert_eq!(output, input);
7813
7814        let mut src_size = input.len() as c_int;
7815        let mut tiny = vec![0u8; compressed_len as usize / 2];
7816        let tiny_len = unsafe {
7817            LZ4_compress_destSize(
7818                input.as_ptr() as *const c_char,
7819                tiny.as_mut_ptr() as *mut c_char,
7820                &mut src_size,
7821                tiny.len() as c_int,
7822            )
7823        };
7824        assert!(tiny_len > 0);
7825        assert!(src_size > 0);
7826        assert!(src_size < input.len() as c_int);
7827
7828        let mut dest_state = vec![0u8; LZ4_sizeofState() as usize];
7829        src_size = input.len() as c_int;
7830        let null_state_len = unsafe {
7831            LZ4_compress_destSize_extState(
7832                std::ptr::null_mut(),
7833                input.as_ptr() as *const c_char,
7834                tiny.as_mut_ptr() as *mut c_char,
7835                &mut src_size,
7836                tiny.len() as c_int,
7837                1,
7838            )
7839        };
7840        assert_eq!(null_state_len, 0);
7841
7842        let mut fast_accel4 = vec![0u8; bound];
7843        let fast_accel4_len = unsafe {
7844            LZ4_compress_fast(
7845                input.as_ptr() as *const c_char,
7846                fast_accel4.as_mut_ptr() as *mut c_char,
7847                input.len() as c_int,
7848                fast_accel4.len() as c_int,
7849                4,
7850            )
7851        };
7852        assert!(fast_accel4_len > 0);
7853
7854        let mut dest_accel4 = vec![0u8; bound];
7855        src_size = input.len() as c_int;
7856        let dest_accel4_len = unsafe {
7857            LZ4_compress_destSize_extState(
7858                dest_state.as_mut_ptr() as *mut c_void,
7859                input.as_ptr() as *const c_char,
7860                dest_accel4.as_mut_ptr() as *mut c_char,
7861                &mut src_size,
7862                dest_accel4.len() as c_int,
7863                4,
7864            )
7865        };
7866        assert_eq!(src_size as usize, input.len());
7867        assert_eq!(dest_accel4_len, fast_accel4_len);
7868        assert!(dest_state.iter().any(|&b| b != 0));
7869        assert_eq!(
7870            &dest_accel4[..dest_accel4_len as usize],
7871            &fast_accel4[..fast_accel4_len as usize]
7872        );
7873
7874        let mut fast_reset = vec![0u8; bound];
7875        let fast_reset_len = unsafe {
7876            LZ4_compress_fast_extState_fastReset(
7877                state.as_mut_ptr() as *mut c_void,
7878                input.as_ptr() as *const c_char,
7879                fast_reset.as_mut_ptr() as *mut c_char,
7880                input.len() as c_int,
7881                fast_reset.len() as c_int,
7882                4,
7883            )
7884        };
7885        assert_eq!(fast_reset_len, fast_accel4_len);
7886        assert_eq!(
7887            &fast_reset[..fast_reset_len as usize],
7888            &fast_accel4[..fast_accel4_len as usize]
7889        );
7890
7891        let mut hc_state = vec![0u8; LZ4_sizeofStateHC() as usize];
7892        let hc_stream =
7893            unsafe { LZ4_initStreamHC(hc_state.as_mut_ptr() as *mut c_void, hc_state.len()) };
7894        assert!(!hc_stream.is_null());
7895        let mut hc_fast_reset = vec![0u8; bound];
7896        let hc_fast_reset_len = unsafe {
7897            LZ4_compress_HC_extStateHC_fastReset(
7898                hc_state.as_mut_ptr() as *mut c_void,
7899                input.as_ptr() as *const c_char,
7900                hc_fast_reset.as_mut_ptr() as *mut c_char,
7901                input.len() as c_int,
7902                hc_fast_reset.len() as c_int,
7903                9,
7904            )
7905        };
7906        assert!(hc_fast_reset_len > 0);
7907        output.fill(0);
7908        let hc_output_len = unsafe {
7909            LZ4_decompress_safe(
7910                hc_fast_reset.as_ptr() as *const c_char,
7911                output.as_mut_ptr() as *mut c_char,
7912                hc_fast_reset_len,
7913                output.len() as c_int,
7914            )
7915        };
7916        assert_eq!(hc_output_len as usize, input.len());
7917        assert_eq!(output, input);
7918    }
7919
7920    #[test]
7921    fn fast_stream_compression_references_loaded_dictionary() {
7922        unsafe {
7923            let dict = b"abcdefghijklmnop";
7924            let input = b"abcdefghijklmnop";
7925            let stream = LZ4_createStream();
7926            assert!(!stream.is_null());
7927            assert_eq!(
7928                LZ4_loadDict(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
7929                dict.len() as c_int
7930            );
7931            let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
7932            let compressed_len = LZ4_compress_continue(
7933                stream,
7934                input.as_ptr() as *const c_char,
7935                compressed.as_mut_ptr() as *mut c_char,
7936                input.len() as c_int,
7937                compressed.len() as c_int,
7938            );
7939            assert!(compressed_len > 0);
7940            assert!((compressed_len as usize) < input.len() + 1);
7941
7942            let mut saved = vec![0u8; dict.len() + input.len()];
7943            let saved_len = LZ4_saveDict(
7944                stream,
7945                saved.as_mut_ptr() as *mut c_char,
7946                saved.len() as c_int,
7947            );
7948            assert!(saved_len >= input.len() as c_int);
7949            LZ4_freeStream(stream);
7950
7951            let mut output = vec![0u8; input.len()];
7952            let output_len = LZ4_decompress_safe_usingDict(
7953                compressed.as_ptr() as *const c_char,
7954                output.as_mut_ptr() as *mut c_char,
7955                compressed_len,
7956                output.len() as c_int,
7957                dict.as_ptr() as *const c_char,
7958                dict.len() as c_int,
7959            );
7960            assert_eq!(output_len as usize, input.len());
7961            assert_eq!(output, input);
7962        }
7963    }
7964
7965    #[test]
7966    fn fast_stream_init_and_tiny_load_dict_match_upstream() {
7967        unsafe {
7968            let mut stream_storage = vec![0u8; LZ4_sizeofStreamState() as usize];
7969            assert!(LZ4_initStream(ptr::null_mut(), stream_storage.len()).is_null());
7970            assert!(LZ4_initStream(stream_storage.as_mut_ptr() as *mut c_void, 1).is_null());
7971            let stream = LZ4_initStream(
7972                stream_storage.as_mut_ptr() as *mut c_void,
7973                stream_storage.len(),
7974            );
7975            assert!(!stream.is_null());
7976
7977            let dict = b"abcdefghijklmnop";
7978            for len in 0..HASH_UNIT {
7979                assert_eq!(
7980                    LZ4_loadDict(stream, dict.as_ptr() as *const c_char, len as c_int),
7981                    0,
7982                    "{len}"
7983                );
7984                assert_eq!(
7985                    LZ4_loadDictSlow(stream, dict.as_ptr() as *const c_char, len as c_int),
7986                    0,
7987                    "{len}"
7988                );
7989            }
7990            assert_eq!(
7991                LZ4_loadDict(stream, dict.as_ptr() as *const c_char, HASH_UNIT as c_int),
7992                HASH_UNIT as c_int
7993            );
7994
7995            let mut hc_storage = vec![0u8; LZ4_sizeofStreamStateHC() as usize];
7996            assert!(LZ4_initStreamHC(ptr::null_mut(), hc_storage.len()).is_null());
7997            assert!(LZ4_initStreamHC(hc_storage.as_mut_ptr() as *mut c_void, 1).is_null());
7998            let hc_stream =
7999                LZ4_initStreamHC(hc_storage.as_mut_ptr() as *mut c_void, hc_storage.len());
8000            assert!(!hc_stream.is_null());
8001            for len in 0..HASH_UNIT {
8002                assert_eq!(
8003                    LZ4_loadDictHC(hc_stream, dict.as_ptr() as *const c_char, len as c_int),
8004                    len as c_int,
8005                    "{len}"
8006                );
8007            }
8008        }
8009    }
8010
8011    #[test]
8012    fn hc_save_dict_treats_tiny_requested_dictionary_as_empty() {
8013        unsafe {
8014            let dict = b"abcdefghijklmnop";
8015            let stream = LZ4_createStreamHC();
8016            assert!(!stream.is_null());
8017            assert_eq!(
8018                LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
8019                dict.len() as c_int
8020            );
8021
8022            let mut saved = [0u8; 16];
8023            assert_eq!(
8024                LZ4_saveDictHC(stream, saved.as_mut_ptr() as *mut c_char, 3),
8025                0
8026            );
8027            assert_eq!(
8028                LZ4_saveDictHC(stream, saved.as_mut_ptr() as *mut c_char, 4),
8029                0
8030            );
8031
8032            assert_eq!(
8033                LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
8034                dict.len() as c_int
8035            );
8036            assert_eq!(
8037                LZ4_saveDictHC(stream, saved.as_mut_ptr() as *mut c_char, 4),
8038                4
8039            );
8040            assert_eq!(&saved[..4], &dict[dict.len() - 4..]);
8041            LZ4_freeStreamHC(stream);
8042        }
8043    }
8044
8045    #[test]
8046    fn decoder_ring_buffer_size_matches_upstream_boundaries() {
8047        let overhead = (LZ4_DISTANCE_MAX + 1 + 14) as c_int;
8048        assert_eq!(LZ4_decoderRingBufferSize(-1), 0);
8049        assert_eq!(LZ4_decoderRingBufferSize(0), overhead + 16);
8050        assert_eq!(LZ4_decoderRingBufferSize(15), overhead + 16);
8051        assert_eq!(LZ4_decoderRingBufferSize(16), overhead + 16);
8052        assert_eq!(LZ4_decoderRingBufferSize(64 * 1024), overhead + 64 * 1024);
8053        assert_eq!(
8054            LZ4_decoderRingBufferSize(LZ4_MAX_INPUT_SIZE),
8055            overhead + LZ4_MAX_INPUT_SIZE
8056        );
8057        assert_eq!(LZ4_decoderRingBufferSize(LZ4_MAX_INPUT_SIZE + 1), 0);
8058    }
8059
8060    #[test]
8061    fn fast_attach_dictionary_is_cleared_after_first_compression() {
8062        unsafe {
8063            let dict = b"abcdefghijklmnop0123456789";
8064            let first = b"zzzzzzzzzzzzzzzz";
8065            let second = b"abcdefghijklmnop0123456789";
8066
8067            let dict_stream = LZ4_createStream();
8068            let work_stream = LZ4_createStream();
8069            assert!(!dict_stream.is_null());
8070            assert!(!work_stream.is_null());
8071            assert_eq!(
8072                LZ4_loadDict(
8073                    dict_stream,
8074                    dict.as_ptr() as *const c_char,
8075                    dict.len() as c_int
8076                ),
8077                dict.len() as c_int
8078            );
8079            LZ4_attach_dictionary(work_stream, dict_stream);
8080
8081            let mut first_compressed = vec![0u8; LZ4_compressBound(first.len() as c_int) as usize];
8082            let first_len = LZ4_compress_fast_continue(
8083                work_stream,
8084                first.as_ptr() as *const c_char,
8085                first_compressed.as_mut_ptr() as *mut c_char,
8086                first.len() as c_int,
8087                first_compressed.len() as c_int,
8088                1,
8089            );
8090            assert!(first_len > 0);
8091
8092            let mut second_compressed =
8093                vec![0u8; LZ4_compressBound(second.len() as c_int) as usize];
8094            let second_len = LZ4_compress_fast_continue(
8095                work_stream,
8096                second.as_ptr() as *const c_char,
8097                second_compressed.as_mut_ptr() as *mut c_char,
8098                second.len() as c_int,
8099                second_compressed.len() as c_int,
8100                1,
8101            );
8102            assert!(second_len > 0);
8103
8104            let mut output = vec![0u8; second.len()];
8105            let output_len = LZ4_decompress_safe(
8106                second_compressed.as_ptr() as *const c_char,
8107                output.as_mut_ptr() as *mut c_char,
8108                second_len,
8109                output.len() as c_int,
8110            );
8111            assert_eq!(output_len, second.len() as c_int);
8112            assert_eq!(output, second);
8113
8114            LZ4_freeStream(dict_stream);
8115            LZ4_freeStream(work_stream);
8116        }
8117    }
8118
8119    #[test]
8120    fn hc_attach_dictionary_is_cleared_after_first_compression() {
8121        unsafe {
8122            let dict = b"abcdefghijklmnop0123456789";
8123            let first = b"zzzzzzzzzzzzzzzz";
8124            let second = b"abcdefghijklmnop0123456789";
8125
8126            let dict_stream = LZ4_createStreamHC();
8127            let work_stream = LZ4_createStreamHC();
8128            assert!(!dict_stream.is_null());
8129            assert!(!work_stream.is_null());
8130            assert_eq!(
8131                LZ4_loadDictHC(
8132                    dict_stream,
8133                    dict.as_ptr() as *const c_char,
8134                    dict.len() as c_int
8135                ),
8136                dict.len() as c_int
8137            );
8138            LZ4_attach_HC_dictionary(work_stream, dict_stream);
8139
8140            let mut first_compressed = vec![0u8; LZ4_compressBound(first.len() as c_int) as usize];
8141            let first_len = LZ4_compress_HC_continue(
8142                work_stream,
8143                first.as_ptr() as *const c_char,
8144                first_compressed.as_mut_ptr() as *mut c_char,
8145                first.len() as c_int,
8146                first_compressed.len() as c_int,
8147            );
8148            assert!(first_len > 0);
8149
8150            let mut second_compressed =
8151                vec![0u8; LZ4_compressBound(second.len() as c_int) as usize];
8152            let second_len = LZ4_compress_HC_continue(
8153                work_stream,
8154                second.as_ptr() as *const c_char,
8155                second_compressed.as_mut_ptr() as *mut c_char,
8156                second.len() as c_int,
8157                second_compressed.len() as c_int,
8158            );
8159            assert!(second_len > 0);
8160
8161            let mut output = vec![0u8; second.len()];
8162            let output_len = LZ4_decompress_safe_usingDict(
8163                second_compressed.as_ptr() as *const c_char,
8164                output.as_mut_ptr() as *mut c_char,
8165                second_len,
8166                output.len() as c_int,
8167                first.as_ptr() as *const c_char,
8168                first.len() as c_int,
8169            );
8170            assert_eq!(output_len, second.len() as c_int);
8171            assert_eq!(output, second);
8172
8173            LZ4_freeStreamHC(dict_stream);
8174            LZ4_freeStreamHC(work_stream);
8175        }
8176    }
8177
8178    #[test]
8179    fn hc_deprecated_continue_uses_stream_dictionary_and_updates_history() {
8180        unsafe {
8181            let dict = b"abcdefghijklmnop0123456789";
8182            let input = b"abcdefghijklmnop0123456789";
8183            let stream = LZ4_createStreamHC();
8184            assert!(!stream.is_null());
8185            assert_eq!(
8186                LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
8187                dict.len() as c_int
8188            );
8189
8190            let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
8191            let compressed_len = LZ4_compressHC2_continue(
8192                stream as *mut c_void,
8193                input.as_ptr() as *const c_char,
8194                compressed.as_mut_ptr() as *mut c_char,
8195                input.len() as c_int,
8196                9,
8197            );
8198            assert!(compressed_len > 0);
8199            assert!((compressed_len as usize) < input.len());
8200
8201            let mut output = vec![0u8; input.len()];
8202            let output_len = LZ4_decompress_safe_usingDict(
8203                compressed.as_ptr() as *const c_char,
8204                output.as_mut_ptr() as *mut c_char,
8205                compressed_len,
8206                output.len() as c_int,
8207                dict.as_ptr() as *const c_char,
8208                dict.len() as c_int,
8209            );
8210            assert_eq!(output_len, input.len() as c_int);
8211            assert_eq!(output, input);
8212
8213            let mut saved = [0u8; 64];
8214            let saved_len = LZ4_saveDictHC(
8215                stream,
8216                saved.as_mut_ptr() as *mut c_char,
8217                saved.len() as c_int,
8218            );
8219            assert!(saved_len >= input.len() as c_int);
8220            let saved_len = saved_len as usize;
8221            assert_eq!(&saved[saved_len - input.len()..saved_len], input);
8222            LZ4_freeStreamHC(stream);
8223        }
8224    }
8225
8226    #[test]
8227    fn hc_favor_decompression_speed_skips_tiny_offsets() {
8228        unsafe {
8229            let input = vec![b'a'; 4096];
8230            let bound = LZ4_compressBound(input.len() as c_int);
8231            let stream = LZ4_createStreamHC();
8232            assert!(!stream.is_null());
8233            LZ4_setCompressionLevel(stream, 10);
8234
8235            let mut normal = vec![0u8; bound as usize];
8236            let normal_len = LZ4_compress_HC_continue(
8237                stream,
8238                input.as_ptr() as *const c_char,
8239                normal.as_mut_ptr() as *mut c_char,
8240                input.len() as c_int,
8241                normal.len() as c_int,
8242            );
8243            assert!(normal_len > 0);
8244            assert!(block_has_offset_below(&normal[..normal_len as usize], 8));
8245
8246            LZ4_resetStreamHC_fast(stream, 10);
8247            LZ4_favorDecompressionSpeed(stream, 1);
8248            let mut favored = vec![0u8; bound as usize];
8249            let favored_len = LZ4_compress_HC_continue(
8250                stream,
8251                input.as_ptr() as *const c_char,
8252                favored.as_mut_ptr() as *mut c_char,
8253                input.len() as c_int,
8254                favored.len() as c_int,
8255            );
8256            assert!(favored_len > 0);
8257            assert!(favored_len >= normal_len);
8258            assert!(!block_has_offset_below(&favored[..favored_len as usize], 8));
8259
8260            LZ4_resetStreamHC(stream, 10);
8261            let mut reset = vec![0u8; bound as usize];
8262            let reset_len = LZ4_compress_HC_continue(
8263                stream,
8264                input.as_ptr() as *const c_char,
8265                reset.as_mut_ptr() as *mut c_char,
8266                input.len() as c_int,
8267                reset.len() as c_int,
8268            );
8269            LZ4_freeStreamHC(stream);
8270
8271            assert_eq!(reset_len, normal_len);
8272            assert_eq!(&reset[..reset_len as usize], &normal[..normal_len as usize]);
8273            assert!(block_has_offset_below(&reset[..reset_len as usize], 8));
8274
8275            let mut output = vec![0u8; input.len()];
8276            let output_len = LZ4_decompress_safe(
8277                favored.as_ptr() as *const c_char,
8278                output.as_mut_ptr() as *mut c_char,
8279                favored_len,
8280                output.len() as c_int,
8281            );
8282            assert_eq!(output_len as usize, input.len());
8283            assert_eq!(output, input);
8284        }
8285    }
8286
8287    #[test]
8288    fn fast_obsolete_slide_input_buffer_reports_saved_dictionary() {
8289        unsafe {
8290            let input = b"slide-input-buffer-dictionary".repeat(4);
8291            let stream = LZ4_createStream();
8292            assert!(!stream.is_null());
8293            assert!(LZ4_slideInputBuffer(stream as *mut c_void).is_null());
8294
8295            let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
8296            let compressed_len = LZ4_compress_fast_continue(
8297                stream,
8298                input.as_ptr() as *const c_char,
8299                compressed.as_mut_ptr() as *mut c_char,
8300                input.len() as c_int,
8301                compressed.len() as c_int,
8302                1,
8303            );
8304            assert!(compressed_len > 0);
8305
8306            let ptr = LZ4_slideInputBuffer(stream as *mut c_void);
8307            assert!(!ptr.is_null());
8308            let saved = slice::from_raw_parts(ptr as *const u8, input.len());
8309            assert_eq!(saved, input);
8310            LZ4_freeStream(stream);
8311        }
8312    }
8313
8314    #[test]
8315    fn fast_deprecated_state_wrappers_round_trip_and_reset() {
8316        unsafe {
8317            let input = b"deprecated-fast-state-wrapper-".repeat(2048);
8318            let bound = LZ4_compressBound(input.len() as c_int) as usize;
8319            let state_size = LZ4_sizeofStreamState();
8320            assert!(state_size > 0);
8321            let mut state = vec![0u8; state_size as usize];
8322
8323            assert_eq!(
8324                LZ4_resetStreamState(
8325                    state.as_mut_ptr() as *mut c_void,
8326                    input.as_ptr() as *mut c_char,
8327                ),
8328                0
8329            );
8330            assert!(LZ4_slideInputBuffer(state.as_mut_ptr() as *mut c_void).is_null());
8331
8332            let mut compressed = vec![0u8; bound];
8333            let compressed_len = LZ4_compress_withState(
8334                state.as_mut_ptr() as *mut c_void,
8335                input.as_ptr() as *const c_char,
8336                compressed.as_mut_ptr() as *mut c_char,
8337                input.len() as c_int,
8338            );
8339            assert!(compressed_len > 0);
8340
8341            let mut output = vec![0u8; input.len()];
8342            let output_len = LZ4_decompress_safe(
8343                compressed.as_ptr() as *const c_char,
8344                output.as_mut_ptr() as *mut c_char,
8345                compressed_len,
8346                output.len() as c_int,
8347            );
8348            assert_eq!(output_len as usize, input.len());
8349            assert_eq!(output, input);
8350
8351            assert_eq!(
8352                LZ4_resetStreamState(state.as_mut_ptr() as *mut c_void, ptr::null_mut(),),
8353                0
8354            );
8355            let mut limited = vec![0u8; compressed_len as usize];
8356            let limited_len = LZ4_compress_limitedOutput_withState(
8357                state.as_mut_ptr() as *mut c_void,
8358                input.as_ptr() as *const c_char,
8359                limited.as_mut_ptr() as *mut c_char,
8360                input.len() as c_int,
8361                limited.len() as c_int,
8362            );
8363            assert!(limited_len > 0);
8364
8365            output.fill(0);
8366            let output_len = LZ4_decompress_safe(
8367                limited.as_ptr() as *const c_char,
8368                output.as_mut_ptr() as *mut c_char,
8369                limited_len,
8370                output.len() as c_int,
8371            );
8372            assert_eq!(output_len as usize, input.len());
8373            assert_eq!(output, input);
8374
8375            let obsolete = LZ4_create(ptr::null_mut());
8376            assert!(!obsolete.is_null());
8377            assert_eq!(LZ4_freeStream(obsolete as *mut LZ4StreamEncode), 0);
8378        }
8379    }
8380
8381    #[test]
8382    fn hc_obsolete_slide_input_buffer_clears_history() {
8383        unsafe {
8384            let dict = b"abcdefghijklmnop0123456789";
8385            let stream = LZ4_createStreamHC();
8386            assert!(!stream.is_null());
8387            assert!(LZ4_slideInputBufferHC(stream as *mut c_void).is_null());
8388            assert_eq!(
8389                LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
8390                dict.len() as c_int
8391            );
8392
8393            let ptr = LZ4_slideInputBufferHC(stream as *mut c_void);
8394            assert!(!ptr.is_null());
8395
8396            let mut compressed = vec![0u8; LZ4_compressBound(dict.len() as c_int) as usize];
8397            let compressed_len = LZ4_compress_HC_continue(
8398                stream,
8399                dict.as_ptr() as *const c_char,
8400                compressed.as_mut_ptr() as *mut c_char,
8401                dict.len() as c_int,
8402                compressed.len() as c_int,
8403            );
8404            assert!(compressed_len > 0);
8405
8406            let mut output = vec![0u8; dict.len()];
8407            let output_len = LZ4_decompress_safe(
8408                compressed.as_ptr() as *const c_char,
8409                output.as_mut_ptr() as *mut c_char,
8410                compressed_len,
8411                output.len() as c_int,
8412            );
8413            assert_eq!(output_len, dict.len() as c_int);
8414            assert_eq!(output, dict);
8415            LZ4_freeStreamHC(stream);
8416        }
8417    }
8418
8419    #[test]
8420    fn decompress_safe_partial_returns_prefix() {
8421        let input = b"partial output data ".repeat(1024);
8422        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
8423        let mut compressed = vec![0u8; bound];
8424        let compressed_len = unsafe {
8425            LZ4_compress_default(
8426                input.as_ptr() as *const c_char,
8427                compressed.as_mut_ptr() as *mut c_char,
8428                input.len() as c_int,
8429                compressed.len() as c_int,
8430            )
8431        };
8432        assert!(compressed_len > 0);
8433        let target = 1234usize;
8434        let mut output = vec![0u8; input.len()];
8435        let output_len = unsafe {
8436            LZ4_decompress_safe_partial(
8437                compressed.as_ptr() as *const c_char,
8438                output.as_mut_ptr() as *mut c_char,
8439                compressed_len,
8440                target as c_int,
8441                output.len() as c_int,
8442            )
8443        };
8444        assert_eq!(output_len as usize, target);
8445        assert_eq!(&output[..target], &input[..target]);
8446    }
8447
8448    #[test]
8449    fn decompress_safe_rejects_block_ending_in_match() {
8450        let compressed = [0x15, b'a', 0x01, 0x00];
8451        let mut output = vec![0u8; 10];
8452
8453        let output_len = unsafe {
8454            LZ4_decompress_safe(
8455                compressed.as_ptr() as *const c_char,
8456                output.as_mut_ptr() as *mut c_char,
8457                compressed.len() as c_int,
8458                output.len() as c_int,
8459            )
8460        };
8461        assert_eq!(output_len, -1);
8462
8463        let dict_compressed = [0x02u8, 0x03, 0x00];
8464        let dict = b"abc";
8465        let mut dict_output = vec![0u8; 6];
8466        let dict_output_len = unsafe {
8467            LZ4_decompress_safe_usingDict(
8468                dict_compressed.as_ptr() as *const c_char,
8469                dict_output.as_mut_ptr() as *mut c_char,
8470                dict_compressed.len() as c_int,
8471                dict_output.len() as c_int,
8472                dict.as_ptr() as *const c_char,
8473                dict.len() as c_int,
8474            )
8475        };
8476        assert_eq!(dict_output_len, -1);
8477    }
8478
8479    #[test]
8480    fn decompress_safe_partial_clamps_target_to_capacity() {
8481        let compressed = [
8482            0xa0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
8483        ];
8484        let mut output = vec![0u8; 5];
8485
8486        let output_len = unsafe {
8487            LZ4_decompress_safe_partial(
8488                compressed.as_ptr() as *const c_char,
8489                output.as_mut_ptr() as *mut c_char,
8490                compressed.len() as c_int,
8491                10,
8492                output.len() as c_int,
8493            )
8494        };
8495        assert_eq!(output_len, output.len() as c_int);
8496        assert_eq!(&output, b"abcde");
8497
8498        let dict_compressed = [0x02u8, 0x03, 0x00];
8499        let dict = b"abc";
8500        let mut dict_output = vec![0u8; 4];
8501        let dict_output_len = unsafe {
8502            LZ4_decompress_safe_partial_usingDict(
8503                dict_compressed.as_ptr() as *const c_char,
8504                dict_output.as_mut_ptr() as *mut c_char,
8505                dict_compressed.len() as c_int,
8506                9,
8507                dict_output.len() as c_int,
8508                dict.as_ptr() as *const c_char,
8509                dict.len() as c_int,
8510            )
8511        };
8512        assert_eq!(dict_output_len, dict_output.len() as c_int);
8513        assert_eq!(&dict_output, b"abca");
8514    }
8515
8516    #[test]
8517    fn decompress_fast_returns_consumed_bytes() {
8518        let input = b"fast decode consumed bytes ".repeat(256);
8519        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
8520        let mut compressed = vec![0u8; bound];
8521        let compressed_len = unsafe {
8522            LZ4_compress_default(
8523                input.as_ptr() as *const c_char,
8524                compressed.as_mut_ptr() as *mut c_char,
8525                input.len() as c_int,
8526                compressed.len() as c_int,
8527            )
8528        };
8529        assert!(compressed_len > 0);
8530        let mut output = vec![0u8; input.len()];
8531        let consumed = unsafe {
8532            LZ4_decompress_fast(
8533                compressed.as_ptr() as *const c_char,
8534                output.as_mut_ptr() as *mut c_char,
8535                output.len() as c_int,
8536            )
8537        };
8538        assert!(consumed > 0);
8539        assert!(consumed <= compressed_len);
8540        assert_eq!(output, input);
8541
8542        let dict = b"abc";
8543        let dict_compressed = [0x30, b'x', b'y', b'z'];
8544        let mut dict_output = vec![0u8; 3];
8545        let consumed = unsafe {
8546            LZ4_decompress_fast_usingDict(
8547                dict_compressed.as_ptr() as *const c_char,
8548                dict_output.as_mut_ptr() as *mut c_char,
8549                dict_output.len() as c_int,
8550                dict.as_ptr() as *const c_char,
8551                dict.len() as c_int,
8552            )
8553        };
8554        assert_eq!(consumed, dict_compressed.len() as c_int);
8555        assert_eq!(dict_output, b"xyz");
8556    }
8557
8558    #[test]
8559    fn decodes_known_empty_block() {
8560        let compressed = [0u8];
8561        let mut output = [];
8562        let len = unsafe {
8563            LZ4_decompress_safe(
8564                compressed.as_ptr() as *const c_char,
8565                output.as_mut_ptr() as *mut c_char,
8566                compressed.len() as c_int,
8567                2,
8568            )
8569        };
8570        assert_eq!(len, 0);
8571    }
8572
8573    #[test]
8574    fn streaming_decode_uses_dictionary() {
8575        unsafe {
8576            let compressed = [0x08, 0x03, 0x00, 0x50, b'a', b'b', b'c', b'd', b'e'];
8577            let expected = b"abcabcabcabcabcde";
8578            let mut direct = vec![0u8; expected.len()];
8579            assert_eq!(
8580                decompress_block_with_dict(&compressed, &mut direct, b"abc"),
8581                Some(expected.len())
8582            );
8583            let dict = b"abc";
8584            let mut using_dict = vec![0u8; expected.len()];
8585            let using_dict_len = LZ4_decompress_safe_usingDict(
8586                compressed.as_ptr() as *const c_char,
8587                using_dict.as_mut_ptr() as *mut c_char,
8588                compressed.len() as c_int,
8589                using_dict.len() as c_int,
8590                dict.as_ptr() as *const c_char,
8591                dict.len() as c_int,
8592            );
8593            assert_eq!(using_dict_len, using_dict.len() as c_int);
8594            assert_eq!(using_dict, expected);
8595
8596            let stream = LZ4_createStreamDecode();
8597            assert!(!stream.is_null());
8598            assert_eq!(
8599                LZ4_setStreamDecode(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
8600                1
8601            );
8602            let mut output = vec![0u8; expected.len()];
8603            let len = LZ4_decompress_safe_continue(
8604                stream,
8605                compressed.as_ptr() as *const c_char,
8606                output.as_mut_ptr() as *mut c_char,
8607                compressed.len() as c_int,
8608                output.len() as c_int,
8609            );
8610            assert_eq!(len, output.len() as c_int);
8611            assert_eq!(output, expected);
8612            LZ4_freeStreamDecode(stream);
8613        }
8614    }
8615
8616    #[test]
8617    fn decompress_match_copy_handles_small_overlap() {
8618        let compressed = [0x15, b'a', 0x01, 0x00, 0x50, b'b', b'c', b'd', b'e', b'f'];
8619        let mut output = vec![0u8; 15];
8620
8621        assert_eq!(decompress_block(&compressed, &mut output), Some(15));
8622        assert_eq!(&output[..10], &[b'a'; 10]);
8623        assert_eq!(&output[10..], b"bcdef");
8624
8625        let mut partial = vec![0u8; 10];
8626        assert_eq!(
8627            decompress_block_partial(&compressed, &mut partial, 7),
8628            Some(7)
8629        );
8630        assert_eq!(&partial[..7], b"aaaaaaa");
8631    }
8632
8633    #[test]
8634    fn decompress_match_copy_spans_dictionary_and_prefix() {
8635        let compressed = [
8636            0x24, b'd', b'e', 0x04, 0x00, 0x50, b'f', b'g', b'h', b'i', b'j',
8637        ];
8638        let mut output = vec![0u8; 15];
8639
8640        assert_eq!(
8641            decompress_block_with_dict(&compressed, &mut output, b"abc"),
8642            Some(15)
8643        );
8644        assert_eq!(output, b"debcdebcdefghij");
8645
8646        let mut fast_output = vec![0u8; 15];
8647        let consumed = unsafe {
8648            LZ4_decompress_fast_usingDict(
8649                compressed.as_ptr() as *const c_char,
8650                fast_output.as_mut_ptr() as *mut c_char,
8651                fast_output.len() as c_int,
8652                b"abc".as_ptr() as *const c_char,
8653                3,
8654            )
8655        };
8656        assert_eq!(consumed, compressed.len() as c_int);
8657        assert_eq!(fast_output, b"debcdebcdefghij");
8658    }
8659
8660    #[test]
8661    fn frame_round_trip_raw_blocks() {
8662        unsafe {
8663            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
8664            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
8665            let prefs = LZ4FPreferences {
8666                frame_info: LZ4FFrameInfo {
8667                    block_size_id: BlockSize::Max64KB,
8668                    block_mode: BlockMode::Independent,
8669                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
8670                    frame_type: FrameType::Frame,
8671                    content_size: 0,
8672                    dict_id: 0,
8673                    block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
8674                },
8675                compression_level: 0,
8676                auto_flush: 0,
8677                favor_dec_speed: 0,
8678                reserved: [0; 3],
8679            };
8680            let input = b"frame data";
8681            let mut encoded = vec![0u8; 128];
8682            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
8683            pos += LZ4F_compressUpdate(
8684                cctx,
8685                encoded.as_mut_ptr().add(pos),
8686                encoded.len() - pos,
8687                input.as_ptr(),
8688                input.len(),
8689                ptr::null(),
8690            );
8691            pos += LZ4F_compressEnd(
8692                cctx,
8693                encoded.as_mut_ptr().add(pos),
8694                encoded.len() - pos,
8695                ptr::null(),
8696            );
8697            encoded.truncate(pos);
8698            LZ4F_freeCompressionContext(cctx);
8699
8700            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
8701            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
8702            let mut out = vec![0u8; input.len()];
8703            let mut dst_size = out.len();
8704            let mut src_size = encoded.len();
8705            let code = LZ4F_decompress(
8706                dctx,
8707                out.as_mut_ptr(),
8708                &mut dst_size,
8709                encoded.as_ptr(),
8710                &mut src_size,
8711                ptr::null(),
8712            );
8713            assert!(!LZ4F_isError(code).eq(&1));
8714            assert_eq!(dst_size, input.len());
8715            assert_eq!(&out, input);
8716            LZ4F_freeDecompressionContext(dctx);
8717        }
8718    }
8719
8720    #[test]
8721    fn frame_rejects_block_larger_than_declared_max_size() {
8722        unsafe {
8723            let prefs = LZ4FPreferences {
8724                frame_info: LZ4FFrameInfo {
8725                    block_size_id: BlockSize::Max64KB,
8726                    block_mode: BlockMode::Independent,
8727                    content_checksum_flag: ContentChecksum::NoChecksum,
8728                    frame_type: FrameType::Frame,
8729                    content_size: 0,
8730                    dict_id: 0,
8731                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
8732                },
8733                compression_level: 0,
8734                auto_flush: 0,
8735                favor_dec_speed: 0,
8736                reserved: [0; 3],
8737            };
8738            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
8739            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
8740            let mut header = vec![0u8; 32];
8741            let header_len = LZ4F_compressBegin(cctx, header.as_mut_ptr(), header.len(), &prefs);
8742            assert_eq!(LZ4F_isError(header_len), 0);
8743            header.truncate(header_len);
8744            LZ4F_freeCompressionContext(cctx);
8745
8746            let too_large_block_len = (64 * 1024 + 1u32).to_le_bytes();
8747            let mut encoded = header.clone();
8748            encoded.extend_from_slice(&too_large_block_len);
8749
8750            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
8751            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
8752            let mut out = [0u8; 1];
8753            let mut dst_size = out.len();
8754            let mut src_size = encoded.len();
8755            let code = LZ4F_decompress(
8756                dctx,
8757                out.as_mut_ptr(),
8758                &mut dst_size,
8759                encoded.as_ptr(),
8760                &mut src_size,
8761                ptr::null(),
8762            );
8763            assert_eq!(code, ERROR_MAX_BLOCK_SIZE_INVALID);
8764            LZ4F_freeDecompressionContext(dctx);
8765
8766            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
8767            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
8768            let mut empty_dst_size = 0usize;
8769            let mut header_src_size = header.len();
8770            let hint = LZ4F_decompress(
8771                dctx,
8772                ptr::null_mut(),
8773                &mut empty_dst_size,
8774                header.as_ptr(),
8775                &mut header_src_size,
8776                ptr::null(),
8777            );
8778            assert_eq!(LZ4F_isError(hint), 0);
8779
8780            let mut dst_size = out.len();
8781            let mut src_size = too_large_block_len.len();
8782            let code = LZ4F_decompress(
8783                dctx,
8784                out.as_mut_ptr(),
8785                &mut dst_size,
8786                too_large_block_len.as_ptr(),
8787                &mut src_size,
8788                ptr::null(),
8789            );
8790            assert_eq!(code, ERROR_MAX_BLOCK_SIZE_INVALID);
8791            LZ4F_freeDecompressionContext(dctx);
8792        }
8793    }
8794
8795    #[test]
8796    fn frame_uncompressed_update_splits_large_raw_blocks() {
8797        unsafe {
8798            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
8799            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
8800            let prefs = LZ4FPreferences {
8801                frame_info: LZ4FFrameInfo {
8802                    block_size_id: BlockSize::Max64KB,
8803                    block_mode: BlockMode::Independent,
8804                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
8805                    frame_type: FrameType::Frame,
8806                    content_size: 0,
8807                    dict_id: 0,
8808                    block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
8809                },
8810                compression_level: 0,
8811                auto_flush: 0,
8812                favor_dec_speed: 0,
8813                reserved: [0; 3],
8814            };
8815            let input = patterned_hc_input(150_000);
8816            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
8817            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
8818            let update = LZ4F_uncompressedUpdate(
8819                cctx,
8820                encoded.as_mut_ptr().add(pos) as *mut c_void,
8821                encoded.len() - pos,
8822                input.as_ptr() as *const c_void,
8823                input.len(),
8824                ptr::null(),
8825            );
8826            assert_eq!(LZ4F_isError(update), 0);
8827            let first_header = u32::from_le_bytes(encoded[pos..pos + 4].try_into().unwrap());
8828            assert_eq!(first_header, 0x8000_0000 | (64 * 1024));
8829            let second_pos = pos + 4 + 64 * 1024 + 4;
8830            let second_header =
8831                u32::from_le_bytes(encoded[second_pos..second_pos + 4].try_into().unwrap());
8832            assert_eq!(second_header, 0x8000_0000 | (64 * 1024));
8833            pos += update;
8834            pos += LZ4F_compressEnd(
8835                cctx,
8836                encoded.as_mut_ptr().add(pos),
8837                encoded.len() - pos,
8838                ptr::null(),
8839            );
8840            encoded.truncate(pos);
8841            LZ4F_freeCompressionContext(cctx);
8842
8843            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
8844            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
8845            let mut output = vec![0u8; input.len()];
8846            let mut src_offset = 0usize;
8847            let mut dst_offset = 0usize;
8848            loop {
8849                let mut src_size = encoded.len() - src_offset;
8850                let mut dst_size = output.len() - dst_offset;
8851                let code = LZ4F_decompress(
8852                    dctx,
8853                    output[dst_offset..].as_mut_ptr(),
8854                    &mut dst_size,
8855                    encoded.as_ptr().add(src_offset),
8856                    &mut src_size,
8857                    ptr::null(),
8858                );
8859                assert_eq!(LZ4F_isError(code), 0);
8860                src_offset += src_size;
8861                dst_offset += dst_size;
8862                if code == 0 {
8863                    break;
8864                }
8865            }
8866            assert_eq!(output, input);
8867            LZ4F_freeDecompressionContext(dctx);
8868        }
8869    }
8870
8871    #[test]
8872    fn frame_single_call_compress_round_trip() {
8873        unsafe {
8874            assert_eq!(LZ4F_getVersion(), LZ4F_VERSION);
8875            assert_eq!(LZ4F_compressionLevel_max(), LZ4HC_CLEVEL_MAX);
8876            let prefs = LZ4FPreferences {
8877                frame_info: LZ4FFrameInfo {
8878                    block_size_id: BlockSize::Max64KB,
8879                    block_mode: BlockMode::Linked,
8880                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
8881                    frame_type: FrameType::Frame,
8882                    content_size: 0,
8883                    dict_id: 0,
8884                    block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
8885                },
8886                compression_level: 9,
8887                auto_flush: 0,
8888                favor_dec_speed: 0,
8889                reserved: [0; 3],
8890            };
8891            let input = b"single call frame compression ".repeat(512);
8892            let bound = LZ4F_compressFrameBound(input.len(), &prefs);
8893            let mut encoded = vec![0u8; bound];
8894            let encoded_len = LZ4F_compressFrame(
8895                encoded.as_mut_ptr() as *mut c_void,
8896                encoded.len(),
8897                input.as_ptr() as *const c_void,
8898                input.len(),
8899                &prefs,
8900            );
8901            assert_eq!(LZ4F_isError(encoded_len), 0);
8902            encoded.truncate(encoded_len);
8903
8904            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
8905            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
8906            let mut output = vec![0u8; input.len()];
8907            let mut src_offset = 0usize;
8908            let mut dst_offset = 0usize;
8909            loop {
8910                let mut src_size = encoded.len() - src_offset;
8911                let mut dst_size = output.len() - dst_offset;
8912                let code = LZ4F_decompress(
8913                    dctx,
8914                    output[dst_offset..].as_mut_ptr(),
8915                    &mut dst_size,
8916                    encoded.as_ptr().add(src_offset),
8917                    &mut src_size,
8918                    ptr::null(),
8919                );
8920                assert_eq!(LZ4F_isError(code), 0);
8921                src_offset += src_size;
8922                dst_offset += dst_size;
8923                if code == 0 {
8924                    break;
8925                }
8926            }
8927            assert_eq!(dst_offset, output.len());
8928            assert_eq!(output, input);
8929            LZ4F_freeDecompressionContext(dctx);
8930        }
8931    }
8932
8933    #[test]
8934    fn frame_header_size_reports_expected_lengths() {
8935        unsafe {
8936            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
8937            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
8938            let prefs = LZ4FPreferences {
8939                frame_info: LZ4FFrameInfo {
8940                    block_size_id: BlockSize::Max64KB,
8941                    block_mode: BlockMode::Independent,
8942                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
8943                    frame_type: FrameType::Frame,
8944                    content_size: 123,
8945                    dict_id: 0,
8946                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
8947                },
8948                compression_level: 0,
8949                auto_flush: 0,
8950                favor_dec_speed: 0,
8951                reserved: [0; 3],
8952            };
8953            let mut encoded = vec![0u8; 32];
8954            let header_len = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
8955            assert_eq!(
8956                LZ4F_headerSize(encoded.as_ptr() as *const c_void, 5),
8957                header_len
8958            );
8959            assert_eq!(
8960                LZ4F_headerSize(encoded.as_ptr() as *const c_void, 4),
8961                ERROR_BAD_HEADER
8962            );
8963            assert_eq!(LZ4F_headerSize(ptr::null(), 5), ERROR_SRC_PTR_WRONG);
8964            let bad_magic = [0u8; 5];
8965            assert_eq!(
8966                LZ4F_headerSize(bad_magic.as_ptr() as *const c_void, bad_magic.len()),
8967                ERROR_FRAME_TYPE_UNKNOWN
8968            );
8969            LZ4F_freeCompressionContext(cctx);
8970
8971            let dict_prefs = LZ4FPreferences {
8972                frame_info: LZ4FFrameInfo {
8973                    block_size_id: BlockSize::Max64KB,
8974                    block_mode: BlockMode::Independent,
8975                    content_checksum_flag: ContentChecksum::NoChecksum,
8976                    frame_type: FrameType::Frame,
8977                    content_size: 0,
8978                    dict_id: 0x11,
8979                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
8980                },
8981                compression_level: 0,
8982                auto_flush: 0,
8983                favor_dec_speed: 0,
8984                reserved: [0; 3],
8985            };
8986            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
8987            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
8988            let header_len =
8989                LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &dict_prefs);
8990            assert_eq!(header_len, 11);
8991            assert_eq!(
8992                LZ4F_headerSize(encoded.as_ptr() as *const c_void, 5),
8993                header_len
8994            );
8995            LZ4F_freeCompressionContext(cctx);
8996
8997            let full_prefs = LZ4FPreferences {
8998                frame_info: LZ4FFrameInfo {
8999                    block_size_id: BlockSize::Max4MB,
9000                    block_mode: BlockMode::Linked,
9001                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
9002                    frame_type: FrameType::Frame,
9003                    content_size: 123,
9004                    dict_id: 0x22,
9005                    block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9006                },
9007                compression_level: 0,
9008                auto_flush: 0,
9009                favor_dec_speed: 0,
9010                reserved: [0; 3],
9011            };
9012            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
9013            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
9014            let header_len =
9015                LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &full_prefs);
9016            assert_eq!(header_len, 19);
9017            assert_eq!(
9018                LZ4F_headerSize(encoded.as_ptr() as *const c_void, 5),
9019                header_len
9020            );
9021            LZ4F_freeCompressionContext(cctx);
9022
9023            let mut skippable = Vec::new();
9024            skippable.extend_from_slice(&LZ4F_SKIPPABLE_MAGIC_MIN.to_le_bytes());
9025            skippable.extend_from_slice(&0u32.to_le_bytes());
9026            assert_eq!(
9027                LZ4F_headerSize(skippable.as_ptr() as *const c_void, skippable.len()),
9028                8
9029            );
9030        }
9031    }
9032
9033    #[test]
9034    fn frame_header_preserves_dict_id_and_rejects_reserved_bits() {
9035        unsafe {
9036            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
9037            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
9038            let prefs = LZ4FPreferences {
9039                frame_info: LZ4FFrameInfo {
9040                    block_size_id: BlockSize::Max256KB,
9041                    block_mode: BlockMode::Independent,
9042                    content_checksum_flag: ContentChecksum::NoChecksum,
9043                    frame_type: FrameType::Frame,
9044                    content_size: 0,
9045                    dict_id: 0x1234_abcd,
9046                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9047                },
9048                compression_level: 0,
9049                auto_flush: 0,
9050                favor_dec_speed: 0,
9051                reserved: [0; 3],
9052            };
9053            let mut encoded = vec![0u8; 32];
9054            let header_len = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
9055            assert_eq!(header_len, 11);
9056            assert_eq!(encoded[4] & 0x01, 0x01);
9057
9058            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9059            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9060            let mut info = LZ4FFrameInfo {
9061                block_size_id: BlockSize::Default,
9062                block_mode: BlockMode::Linked,
9063                content_checksum_flag: ContentChecksum::ChecksumEnabled,
9064                frame_type: FrameType::SkippableFrame,
9065                content_size: 1,
9066                dict_id: 0,
9067                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9068            };
9069            let mut src_size = header_len;
9070            assert_eq!(
9071                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut src_size),
9072                0
9073            );
9074            assert_eq!(src_size, header_len);
9075            assert_eq!(info.dict_id, 0x1234_abcd);
9076            assert!(matches!(info.block_size_id, BlockSize::Max256KB));
9077            LZ4F_freeDecompressionContext(dctx);
9078
9079            let mut bad_flg = encoded[..header_len].to_vec();
9080            bad_flg[4] |= 0x02;
9081            let hc = (xxhash32(&bad_flg[4..header_len - 1], 0) >> 8) as u8;
9082            bad_flg[header_len - 1] = hc;
9083            let mut bad_size = bad_flg.len();
9084            assert_eq!(
9085                LZ4F_getFrameInfo(
9086                    LZ4FDecompressionContext(ptr::null_mut()),
9087                    ptr::null_mut(),
9088                    ptr::null(),
9089                    ptr::null_mut(),
9090                ),
9091                ERROR_PARAMETER_NULL
9092            );
9093            let mut dctx_bad = LZ4FDecompressionContext(ptr::null_mut());
9094            assert_eq!(
9095                LZ4F_createDecompressionContext(&mut dctx_bad, LZ4F_VERSION),
9096                0
9097            );
9098            assert_eq!(
9099                LZ4F_getFrameInfo(dctx_bad, &mut info, bad_flg.as_ptr(), &mut bad_size),
9100                ERROR_RESERVED_FLAG_SET
9101            );
9102
9103            let mut bad_bd = encoded[..header_len].to_vec();
9104            bad_bd[5] |= 0x01;
9105            let hc = (xxhash32(&bad_bd[4..header_len - 1], 0) >> 8) as u8;
9106            bad_bd[header_len - 1] = hc;
9107            bad_size = bad_bd.len();
9108            assert_eq!(
9109                LZ4F_getFrameInfo(dctx_bad, &mut info, bad_bd.as_ptr(), &mut bad_size),
9110                ERROR_RESERVED_FLAG_SET
9111            );
9112
9113            let mut bad_version = encoded[..header_len].to_vec();
9114            bad_version[4] = (bad_version[4] & !0xC0) | 0x80;
9115            let hc = (xxhash32(&bad_version[4..header_len - 1], 0) >> 8) as u8;
9116            bad_version[header_len - 1] = hc;
9117            bad_size = bad_version.len();
9118            assert_eq!(
9119                LZ4F_getFrameInfo(dctx_bad, &mut info, bad_version.as_ptr(), &mut bad_size),
9120                ERROR_HEADER_VERSION_WRONG
9121            );
9122            let mut dst = [0u8; 1];
9123            let mut dst_size = dst.len();
9124            let mut src_size = bad_version.len();
9125            assert_eq!(
9126                LZ4F_decompress(
9127                    dctx_bad,
9128                    dst.as_mut_ptr(),
9129                    &mut dst_size,
9130                    bad_version.as_ptr(),
9131                    &mut src_size,
9132                    ptr::null(),
9133                ),
9134                ERROR_HEADER_VERSION_WRONG
9135            );
9136
9137            LZ4F_resetDecompressionContext(dctx_bad);
9138            let mut bad_block_size = encoded[..header_len].to_vec();
9139            bad_block_size[5] = (bad_block_size[5] & 0x0f) | (3 << 4);
9140            let hc = (xxhash32(&bad_block_size[4..header_len - 1], 0) >> 8) as u8;
9141            bad_block_size[header_len - 1] = hc;
9142            bad_size = bad_block_size.len();
9143            assert_eq!(
9144                LZ4F_getFrameInfo(dctx_bad, &mut info, bad_block_size.as_ptr(), &mut bad_size),
9145                ERROR_MAX_BLOCK_SIZE_INVALID
9146            );
9147            let mut dst_size = dst.len();
9148            let mut src_size = bad_block_size.len();
9149            assert_eq!(
9150                LZ4F_decompress(
9151                    dctx_bad,
9152                    dst.as_mut_ptr(),
9153                    &mut dst_size,
9154                    bad_block_size.as_ptr(),
9155                    &mut src_size,
9156                    ptr::null(),
9157                ),
9158                ERROR_MAX_BLOCK_SIZE_INVALID
9159            );
9160
9161            LZ4F_resetDecompressionContext(dctx_bad);
9162            let mut bad_magic = encoded[..header_len].to_vec();
9163            bad_magic[..4].copy_from_slice(b"bad!");
9164            bad_size = bad_magic.len();
9165            assert_eq!(
9166                LZ4F_getFrameInfo(dctx_bad, &mut info, bad_magic.as_ptr(), &mut bad_size),
9167                ERROR_FRAME_TYPE_UNKNOWN
9168            );
9169            assert_eq!(bad_size, 0);
9170            let mut dst_size = dst.len();
9171            let mut src_size = bad_magic.len();
9172            assert_eq!(
9173                LZ4F_decompress(
9174                    dctx_bad,
9175                    dst.as_mut_ptr(),
9176                    &mut dst_size,
9177                    bad_magic.as_ptr(),
9178                    &mut src_size,
9179                    ptr::null(),
9180                ),
9181                ERROR_FRAME_TYPE_UNKNOWN
9182            );
9183            LZ4F_freeDecompressionContext(dctx_bad);
9184            LZ4F_freeCompressionContext(cctx);
9185        }
9186    }
9187
9188    #[test]
9189    fn frame_info_errors_zero_consumed_and_preserve_context() {
9190        unsafe {
9191            let input = b"context survives bad frame info";
9192            let prefs = LZ4FPreferences {
9193                frame_info: LZ4FFrameInfo {
9194                    block_size_id: BlockSize::Max64KB,
9195                    block_mode: BlockMode::Independent,
9196                    content_checksum_flag: ContentChecksum::NoChecksum,
9197                    frame_type: FrameType::Frame,
9198                    content_size: input.len() as u64,
9199                    dict_id: 0,
9200                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9201                },
9202                compression_level: 0,
9203                auto_flush: 0,
9204                favor_dec_speed: 0,
9205                reserved: [0; 3],
9206            };
9207            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
9208            let encoded_len = LZ4F_compressFrame(
9209                encoded.as_mut_ptr() as *mut c_void,
9210                encoded.len(),
9211                input.as_ptr() as *const c_void,
9212                input.len(),
9213                &prefs,
9214            );
9215            assert_eq!(LZ4F_isError(encoded_len), 0);
9216            encoded.truncate(encoded_len);
9217
9218            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9219            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9220            let mut info = LZ4FFrameInfo {
9221                block_size_id: BlockSize::Default,
9222                block_mode: BlockMode::Linked,
9223                content_checksum_flag: ContentChecksum::ChecksumEnabled,
9224                frame_type: FrameType::SkippableFrame,
9225                content_size: 99,
9226                dict_id: 99,
9227                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9228            };
9229            let mut short_size = 6usize;
9230            assert_eq!(
9231                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut short_size),
9232                ERROR_BAD_HEADER
9233            );
9234            assert_eq!(short_size, 0);
9235            assert!(matches!(info.frame_type, FrameType::SkippableFrame));
9236
9237            let mut consumed = encoded.len();
9238            assert_eq!(
9239                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut consumed),
9240                0
9241            );
9242            assert_eq!(consumed, 15);
9243
9244            let mut output = vec![0u8; input.len()];
9245            let mut src_size = encoded.len() - consumed;
9246            let mut dst_size = output.len();
9247            let code = LZ4F_decompress(
9248                dctx,
9249                output.as_mut_ptr(),
9250                &mut dst_size,
9251                encoded.as_ptr().add(consumed),
9252                &mut src_size,
9253                ptr::null(),
9254            );
9255            assert_eq!(LZ4F_isError(code), 0);
9256            assert_eq!(output, input);
9257            LZ4F_freeDecompressionContext(dctx);
9258        }
9259    }
9260
9261    #[test]
9262    fn frame_info_rejects_context_after_partial_header_decode_started() {
9263        unsafe {
9264            let input = b"partial header get frame info";
9265            let prefs = LZ4FPreferences {
9266                frame_info: LZ4FFrameInfo {
9267                    block_size_id: BlockSize::Max64KB,
9268                    block_mode: BlockMode::Independent,
9269                    content_checksum_flag: ContentChecksum::NoChecksum,
9270                    frame_type: FrameType::Frame,
9271                    content_size: input.len() as u64,
9272                    dict_id: 0,
9273                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9274                },
9275                compression_level: 0,
9276                auto_flush: 0,
9277                favor_dec_speed: 0,
9278                reserved: [0; 3],
9279            };
9280            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
9281            let encoded_len = LZ4F_compressFrame(
9282                encoded.as_mut_ptr() as *mut c_void,
9283                encoded.len(),
9284                input.as_ptr() as *const c_void,
9285                input.len(),
9286                &prefs,
9287            );
9288            assert_eq!(LZ4F_isError(encoded_len), 0);
9289            encoded.truncate(encoded_len);
9290
9291            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9292            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9293            let mut output = [0u8; 1];
9294            let mut src_size = 5usize;
9295            let mut dst_size = output.len();
9296            let hint = LZ4F_decompress(
9297                dctx,
9298                output.as_mut_ptr(),
9299                &mut dst_size,
9300                encoded.as_ptr(),
9301                &mut src_size,
9302                ptr::null(),
9303            );
9304            assert_eq!(LZ4F_isError(hint), 0);
9305            assert_eq!(src_size, 5);
9306            assert_eq!(dst_size, 0);
9307
9308            let mut info = LZ4FFrameInfo {
9309                block_size_id: BlockSize::Default,
9310                block_mode: BlockMode::Linked,
9311                content_checksum_flag: ContentChecksum::ChecksumEnabled,
9312                frame_type: FrameType::SkippableFrame,
9313                content_size: 99,
9314                dict_id: 99,
9315                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9316            };
9317            let mut consumed = encoded.len();
9318            assert_eq!(
9319                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut consumed),
9320                ERROR_FRAME_DECODING_ALREADY_STARTED
9321            );
9322            assert_eq!(consumed, 0);
9323            assert!(matches!(info.frame_type, FrameType::SkippableFrame));
9324            LZ4F_freeDecompressionContext(dctx);
9325        }
9326    }
9327
9328    #[test]
9329    fn frame_using_dict_round_trip_first_independent_block() {
9330        unsafe {
9331            let dict = b"abcdefghijklmnop";
9332            let input = b"abcdefghijklmnop";
9333            let prefs = LZ4FPreferences {
9334                frame_info: LZ4FFrameInfo {
9335                    block_size_id: BlockSize::Max64KB,
9336                    block_mode: BlockMode::Independent,
9337                    content_checksum_flag: ContentChecksum::NoChecksum,
9338                    frame_type: FrameType::Frame,
9339                    content_size: input.len() as u64,
9340                    dict_id: 0,
9341                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9342                },
9343                compression_level: 9,
9344                auto_flush: 0,
9345                favor_dec_speed: 0,
9346                reserved: [0; 3],
9347            };
9348
9349            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
9350            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
9351            let mut encoded = vec![0u8; 256];
9352            let mut pos = LZ4F_compressBegin_usingDict(
9353                cctx,
9354                encoded.as_mut_ptr() as *mut c_void,
9355                encoded.len(),
9356                dict.as_ptr() as *const c_void,
9357                dict.len(),
9358                &prefs,
9359            );
9360            assert_eq!(LZ4F_isError(pos), 0);
9361            let update_len = LZ4F_compressUpdate(
9362                cctx,
9363                encoded.as_mut_ptr().add(pos),
9364                encoded.len() - pos,
9365                input.as_ptr(),
9366                input.len(),
9367                ptr::null(),
9368            );
9369            assert_eq!(LZ4F_isError(update_len), 0);
9370            let block_header = u32::from_le_bytes(encoded[pos..pos + 4].try_into().unwrap());
9371            assert_eq!(block_header & 0x8000_0000, 0);
9372            assert!((block_header as usize) < input.len());
9373            pos += update_len;
9374            pos += LZ4F_compressEnd(
9375                cctx,
9376                encoded.as_mut_ptr().add(pos),
9377                encoded.len() - pos,
9378                ptr::null(),
9379            );
9380            encoded.truncate(pos);
9381            LZ4F_freeCompressionContext(cctx);
9382
9383            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9384            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9385            let mut output = vec![0u8; input.len()];
9386            let mut dst_size = output.len();
9387            let mut src_size = encoded.len();
9388            let code = LZ4F_decompress_usingDict(
9389                dctx,
9390                output.as_mut_ptr() as *mut c_void,
9391                &mut dst_size,
9392                encoded.as_ptr() as *const c_void,
9393                &mut src_size,
9394                dict.as_ptr() as *const c_void,
9395                dict.len(),
9396                ptr::null(),
9397            );
9398            assert_eq!(LZ4F_isError(code), 0);
9399            assert_eq!(dst_size, input.len());
9400            assert_eq!(output, input);
9401            LZ4F_freeDecompressionContext(dctx);
9402        }
9403    }
9404
9405    #[test]
9406    fn frame_cdict_round_trip() {
9407        unsafe {
9408            let dict = b"abcdefghijklmnop";
9409            let input = b"abcdefghijklmnop";
9410            let cdict = LZ4F_createCDict(dict.as_ptr() as *const c_void, dict.len());
9411            assert!(!cdict.is_null());
9412            let prefs = LZ4FPreferences {
9413                frame_info: LZ4FFrameInfo {
9414                    block_size_id: BlockSize::Max64KB,
9415                    block_mode: BlockMode::Independent,
9416                    content_checksum_flag: ContentChecksum::NoChecksum,
9417                    frame_type: FrameType::Frame,
9418                    content_size: input.len() as u64,
9419                    dict_id: 0,
9420                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9421                },
9422                compression_level: 9,
9423                auto_flush: 0,
9424                favor_dec_speed: 0,
9425                reserved: [0; 3],
9426            };
9427
9428            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
9429            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
9430            let mut encoded = vec![0u8; 256];
9431            let encoded_len = LZ4F_compressFrame_usingCDict(
9432                cctx,
9433                encoded.as_mut_ptr() as *mut c_void,
9434                encoded.len(),
9435                input.as_ptr() as *const c_void,
9436                input.len(),
9437                cdict,
9438                &prefs,
9439            );
9440            assert_eq!(LZ4F_isError(encoded_len), 0);
9441            encoded.truncate(encoded_len);
9442            LZ4F_freeCompressionContext(cctx);
9443            LZ4F_freeCDict(cdict);
9444
9445            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9446            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9447            let mut output = vec![0u8; input.len()];
9448            let mut dst_size = output.len();
9449            let mut src_size = encoded.len();
9450            let code = LZ4F_decompress_usingDict(
9451                dctx,
9452                output.as_mut_ptr() as *mut c_void,
9453                &mut dst_size,
9454                encoded.as_ptr() as *const c_void,
9455                &mut src_size,
9456                dict.as_ptr() as *const c_void,
9457                dict.len(),
9458                ptr::null(),
9459            );
9460            assert_eq!(LZ4F_isError(code), 0);
9461            assert_eq!(dst_size, input.len());
9462            assert_eq!(output, input);
9463            LZ4F_freeDecompressionContext(dctx);
9464        }
9465    }
9466
9467    #[test]
9468    fn frame_skippable_frame_is_skipped() {
9469        unsafe {
9470            let mut skippable = Vec::new();
9471            skippable.extend_from_slice(&LZ4F_SKIPPABLE_MAGIC_MIN.to_le_bytes());
9472            skippable.extend_from_slice(&5u32.to_le_bytes());
9473            skippable.extend_from_slice(b"abcde");
9474
9475            let mut dctx_info = LZ4FDecompressionContext(ptr::null_mut());
9476            assert_eq!(
9477                LZ4F_createDecompressionContext(&mut dctx_info, LZ4F_VERSION),
9478                0
9479            );
9480            let mut info = LZ4FFrameInfo {
9481                block_size_id: BlockSize::Default,
9482                block_mode: BlockMode::Independent,
9483                content_checksum_flag: ContentChecksum::NoChecksum,
9484                frame_type: FrameType::Frame,
9485                content_size: 0,
9486                dict_id: 0,
9487                block_checksum_flag: BlockChecksum::NoBlockChecksum,
9488            };
9489            let mut info_src_size = skippable.len();
9490            assert_eq!(
9491                LZ4F_getFrameInfo(dctx_info, &mut info, skippable.as_ptr(), &mut info_src_size),
9492                0
9493            );
9494            assert!(matches!(info.frame_type, FrameType::SkippableFrame));
9495            assert_eq!(info_src_size, skippable.len());
9496            LZ4F_freeDecompressionContext(dctx_info);
9497
9498            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9499            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9500            let mut output = [0u8; 1];
9501            let mut dst_size = output.len();
9502            let mut src_size = skippable.len();
9503            let code = LZ4F_decompress(
9504                dctx,
9505                output.as_mut_ptr(),
9506                &mut dst_size,
9507                skippable.as_ptr(),
9508                &mut src_size,
9509                ptr::null(),
9510            );
9511            assert_eq!(code, 0);
9512            assert_eq!(dst_size, 0);
9513            assert_eq!(src_size, skippable.len());
9514            LZ4F_freeDecompressionContext(dctx);
9515        }
9516    }
9517
9518    #[test]
9519    fn frame_info_consumes_header_for_subsequent_decompress() {
9520        unsafe {
9521            let input = patterned_hc_input(96 * 1024);
9522            let prefs = LZ4FPreferences {
9523                frame_info: LZ4FFrameInfo {
9524                    block_size_id: BlockSize::Max64KB,
9525                    block_mode: BlockMode::Independent,
9526                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
9527                    frame_type: FrameType::Frame,
9528                    content_size: input.len() as u64,
9529                    dict_id: 0xfeed_beef,
9530                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9531                },
9532                compression_level: 0,
9533                auto_flush: 0,
9534                favor_dec_speed: 0,
9535                reserved: [0; 3],
9536            };
9537            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
9538            let encoded_len = LZ4F_compressFrame(
9539                encoded.as_mut_ptr() as *mut c_void,
9540                encoded.len(),
9541                input.as_ptr() as *const c_void,
9542                input.len(),
9543                &prefs,
9544            );
9545            assert_eq!(LZ4F_isError(encoded_len), 0);
9546            encoded.truncate(encoded_len);
9547
9548            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9549            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9550            let mut info = LZ4FFrameInfo {
9551                block_size_id: BlockSize::Default,
9552                block_mode: BlockMode::Linked,
9553                content_checksum_flag: ContentChecksum::NoChecksum,
9554                frame_type: FrameType::SkippableFrame,
9555                content_size: 1,
9556                dict_id: 123,
9557                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9558            };
9559            let mut consumed = encoded.len();
9560            assert_eq!(
9561                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut consumed),
9562                0
9563            );
9564            assert_eq!(consumed, 19);
9565            assert!(matches!(info.frame_type, FrameType::Frame));
9566            assert!(matches!(info.block_size_id, BlockSize::Max64KB));
9567            assert_eq!(info.content_size, input.len() as u64);
9568            assert_eq!(info.dict_id, 0xfeed_beef);
9569
9570            let mut second_consumed = 123usize;
9571            let second_hint = LZ4F_getFrameInfo(dctx, &mut info, ptr::null(), &mut second_consumed);
9572            assert_eq!(LZ4F_isError(second_hint), 0);
9573            assert_eq!(second_consumed, 0);
9574            assert_eq!(second_hint, 4);
9575            assert_eq!(info.content_size, input.len() as u64);
9576            assert_eq!(info.dict_id, 0xfeed_beef);
9577
9578            let mut output = vec![0u8; input.len()];
9579            let mut src_offset = consumed;
9580            let mut dst_offset = 0usize;
9581            loop {
9582                let mut src_size = encoded.len() - src_offset;
9583                let mut dst_size = output.len() - dst_offset;
9584                let code = LZ4F_decompress(
9585                    dctx,
9586                    output[dst_offset..].as_mut_ptr(),
9587                    &mut dst_size,
9588                    encoded.as_ptr().add(src_offset),
9589                    &mut src_size,
9590                    ptr::null(),
9591                );
9592                assert_eq!(LZ4F_isError(code), 0);
9593                src_offset += src_size;
9594                dst_offset += dst_size;
9595                if code == 0 {
9596                    break;
9597                }
9598            }
9599            assert_eq!(src_offset, encoded.len());
9600            assert_eq!(output, input);
9601            LZ4F_freeDecompressionContext(dctx);
9602        }
9603    }
9604
9605    #[test]
9606    fn frame_info_reports_state_after_partial_decode_and_reset() {
9607        unsafe {
9608            let input = patterned_hc_input(80 * 1024);
9609            let prefs = LZ4FPreferences {
9610                frame_info: LZ4FFrameInfo {
9611                    block_size_id: BlockSize::Max64KB,
9612                    block_mode: BlockMode::Linked,
9613                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
9614                    frame_type: FrameType::Frame,
9615                    content_size: input.len() as u64,
9616                    dict_id: 0x0102_0304,
9617                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9618                },
9619                compression_level: 0,
9620                auto_flush: 0,
9621                favor_dec_speed: 0,
9622                reserved: [0; 3],
9623            };
9624            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
9625            let encoded_len = LZ4F_compressFrame(
9626                encoded.as_mut_ptr() as *mut c_void,
9627                encoded.len(),
9628                input.as_ptr() as *const c_void,
9629                input.len(),
9630                &prefs,
9631            );
9632            assert_eq!(LZ4F_isError(encoded_len), 0);
9633            encoded.truncate(encoded_len);
9634
9635            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9636            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9637            let mut info = LZ4FFrameInfo {
9638                block_size_id: BlockSize::Default,
9639                block_mode: BlockMode::Independent,
9640                content_checksum_flag: ContentChecksum::NoChecksum,
9641                frame_type: FrameType::SkippableFrame,
9642                content_size: 0,
9643                dict_id: 0,
9644                block_checksum_flag: BlockChecksum::NoBlockChecksum,
9645            };
9646            let mut consumed = encoded.len();
9647            assert_eq!(
9648                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut consumed),
9649                0
9650            );
9651            assert_eq!(consumed, 19);
9652            assert!(matches!(info.block_mode, BlockMode::Linked));
9653            assert_eq!(info.content_size, input.len() as u64);
9654            assert_eq!(info.dict_id, 0x0102_0304);
9655
9656            let options = LZ4FDecompressOptions {
9657                stable_dst: 1,
9658                skipChecksums: 0,
9659                reserved: [0; 2],
9660            };
9661            let mut output = [0u8; 37];
9662            let mut src_size = encoded.len() - consumed;
9663            let mut dst_size = output.len();
9664            let hint = LZ4F_decompress(
9665                dctx,
9666                output.as_mut_ptr(),
9667                &mut dst_size,
9668                encoded.as_ptr().add(consumed),
9669                &mut src_size,
9670                &options,
9671            );
9672            assert_eq!(LZ4F_isError(hint), 0);
9673            assert_eq!(dst_size, output.len());
9674
9675            let mut second_consumed = usize::MAX;
9676            let second_hint = LZ4F_getFrameInfo(dctx, &mut info, ptr::null(), &mut second_consumed);
9677            assert_eq!(LZ4F_isError(second_hint), 0);
9678            assert_eq!(second_consumed, 0);
9679            assert!(matches!(info.block_mode, BlockMode::Linked));
9680            assert_eq!(info.content_size, input.len() as u64);
9681            assert_eq!(info.dict_id, 0x0102_0304);
9682
9683            LZ4F_resetDecompressionContext(dctx);
9684            second_consumed = usize::MAX;
9685            assert_eq!(
9686                LZ4F_getFrameInfo(dctx, &mut info, ptr::null(), &mut second_consumed),
9687                ERROR_SRC_PTR_WRONG
9688            );
9689            assert_eq!(second_consumed, 0);
9690            LZ4F_freeDecompressionContext(dctx);
9691        }
9692    }
9693
9694    #[test]
9695    fn frame_dict_id_decodes_from_upstream_generated_fixture() {
9696        unsafe {
9697            let encoded = decode_hex(
9698                "04224d186d401b00000000000000adfbcadeba1b0000806672616d65207769746820757073747265616d206469637420696400000000af334300",
9699            );
9700            let expected = b"frame with upstream dict id";
9701            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9702            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9703            let mut info = LZ4FFrameInfo {
9704                block_size_id: BlockSize::Default,
9705                block_mode: BlockMode::Linked,
9706                content_checksum_flag: ContentChecksum::NoChecksum,
9707                frame_type: FrameType::SkippableFrame,
9708                content_size: 0,
9709                dict_id: 0,
9710                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9711            };
9712            let mut consumed = encoded.len();
9713            assert_eq!(
9714                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut consumed),
9715                0
9716            );
9717            assert_eq!(consumed, 19);
9718            assert!(matches!(info.block_size_id, BlockSize::Max64KB));
9719            assert!(matches!(info.block_mode, BlockMode::Independent));
9720            assert!(matches!(
9721                info.content_checksum_flag,
9722                ContentChecksum::ChecksumEnabled
9723            ));
9724            assert_eq!(info.content_size, expected.len() as u64);
9725            assert_eq!(info.dict_id, 0xdecafbad);
9726
9727            let mut output = vec![0u8; expected.len()];
9728            let mut src_offset = consumed;
9729            let mut dst_offset = 0usize;
9730            loop {
9731                let mut src_size = encoded.len() - src_offset;
9732                let mut dst_size = output.len() - dst_offset;
9733                let code = LZ4F_decompress(
9734                    dctx,
9735                    output[dst_offset..].as_mut_ptr(),
9736                    &mut dst_size,
9737                    encoded.as_ptr().add(src_offset),
9738                    &mut src_size,
9739                    ptr::null(),
9740                );
9741                assert_eq!(LZ4F_isError(code), 0);
9742                src_offset += src_size;
9743                dst_offset += dst_size;
9744                if code == 0 {
9745                    break;
9746                }
9747            }
9748            assert_eq!(src_offset, encoded.len());
9749            assert_eq!(dst_offset, expected.len());
9750            assert_eq!(output, expected);
9751            LZ4F_freeDecompressionContext(dctx);
9752        }
9753    }
9754
9755    #[test]
9756    fn frame_decompress_resets_context_after_complete_frame() {
9757        unsafe {
9758            let first = b"first complete frame";
9759            let second = b"second complete frame";
9760            let prefs = LZ4FPreferences {
9761                frame_info: LZ4FFrameInfo {
9762                    block_size_id: BlockSize::Max64KB,
9763                    block_mode: BlockMode::Independent,
9764                    content_checksum_flag: ContentChecksum::NoChecksum,
9765                    frame_type: FrameType::Frame,
9766                    content_size: 0,
9767                    dict_id: 0,
9768                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9769                },
9770                compression_level: 0,
9771                auto_flush: 0,
9772                favor_dec_speed: 0,
9773                reserved: [0; 3],
9774            };
9775            let mut first_frame = vec![0u8; LZ4F_compressBound(first.len(), &prefs) + 32];
9776            let first_len = LZ4F_compressFrame(
9777                first_frame.as_mut_ptr() as *mut c_void,
9778                first_frame.len(),
9779                first.as_ptr() as *const c_void,
9780                first.len(),
9781                &prefs,
9782            );
9783            assert_eq!(LZ4F_isError(first_len), 0);
9784            first_frame.truncate(first_len);
9785
9786            let mut second_frame = vec![0u8; LZ4F_compressBound(second.len(), &prefs) + 32];
9787            let second_len = LZ4F_compressFrame(
9788                second_frame.as_mut_ptr() as *mut c_void,
9789                second_frame.len(),
9790                second.as_ptr() as *const c_void,
9791                second.len(),
9792                &prefs,
9793            );
9794            assert_eq!(LZ4F_isError(second_len), 0);
9795            second_frame.truncate(second_len);
9796
9797            let mut concatenated = first_frame.clone();
9798            concatenated.extend_from_slice(&second_frame);
9799
9800            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9801            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9802            let mut output = vec![0u8; first.len()];
9803            let mut src_offset = 0usize;
9804            let mut dst_offset = 0usize;
9805            loop {
9806                let mut src_size = first_frame.len() - src_offset;
9807                let mut dst_size = output.len() - dst_offset;
9808                let code = LZ4F_decompress(
9809                    dctx,
9810                    output[dst_offset..].as_mut_ptr(),
9811                    &mut dst_size,
9812                    concatenated.as_ptr().add(src_offset),
9813                    &mut src_size,
9814                    ptr::null(),
9815                );
9816                assert_eq!(LZ4F_isError(code), 0);
9817                src_offset += src_size;
9818                dst_offset += dst_size;
9819                if code == 0 {
9820                    break;
9821                }
9822            }
9823            assert_eq!(src_offset, first_frame.len());
9824            assert_eq!(output, first);
9825
9826            let mut info = LZ4FFrameInfo {
9827                block_size_id: BlockSize::Default,
9828                block_mode: BlockMode::Linked,
9829                content_checksum_flag: ContentChecksum::ChecksumEnabled,
9830                frame_type: FrameType::SkippableFrame,
9831                content_size: 1,
9832                dict_id: 1,
9833                block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
9834            };
9835            let mut consumed = usize::MAX;
9836            assert_eq!(
9837                LZ4F_getFrameInfo(dctx, &mut info, ptr::null(), &mut consumed),
9838                ERROR_SRC_PTR_WRONG
9839            );
9840            assert_eq!(consumed, 0);
9841
9842            let mut output = vec![0u8; second.len()];
9843            src_offset = first_frame.len();
9844            let mut dst_offset = 0usize;
9845            loop {
9846                let mut src_size = concatenated.len() - src_offset;
9847                let mut dst_size = output.len() - dst_offset;
9848                let code = LZ4F_decompress(
9849                    dctx,
9850                    output[dst_offset..].as_mut_ptr(),
9851                    &mut dst_size,
9852                    concatenated.as_ptr().add(src_offset),
9853                    &mut src_size,
9854                    ptr::null(),
9855                );
9856                assert_eq!(LZ4F_isError(code), 0);
9857                src_offset += src_size;
9858                dst_offset += dst_size;
9859                if code == 0 {
9860                    break;
9861                }
9862            }
9863            assert_eq!(src_offset, concatenated.len());
9864            assert_eq!(output, second);
9865            LZ4F_freeDecompressionContext(dctx);
9866        }
9867    }
9868
9869    #[test]
9870    fn frame_free_decompression_context_reports_incomplete_frame() {
9871        unsafe {
9872            let input = b"incomplete frame free status";
9873            let prefs = LZ4FPreferences {
9874                frame_info: LZ4FFrameInfo {
9875                    block_size_id: BlockSize::Max64KB,
9876                    block_mode: BlockMode::Independent,
9877                    content_checksum_flag: ContentChecksum::NoChecksum,
9878                    frame_type: FrameType::Frame,
9879                    content_size: 0,
9880                    dict_id: 0,
9881                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9882                },
9883                compression_level: 0,
9884                auto_flush: 0,
9885                favor_dec_speed: 0,
9886                reserved: [0; 3],
9887            };
9888            let mut frame = vec![0u8; LZ4F_compressBound(input.len(), &prefs)];
9889            let frame_len = LZ4F_compressFrame(
9890                frame.as_mut_ptr() as *mut c_void,
9891                frame.len(),
9892                input.as_ptr() as *const c_void,
9893                input.len(),
9894                &prefs,
9895            );
9896            assert_eq!(LZ4F_isError(frame_len), 0);
9897            frame.truncate(frame_len);
9898
9899            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9900            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9901            assert_eq!(LZ4F_freeDecompressionContext(dctx), 0);
9902
9903            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9904            let mut info = LZ4FFrameInfo {
9905                block_size_id: BlockSize::Default,
9906                block_mode: BlockMode::Linked,
9907                content_checksum_flag: ContentChecksum::NoChecksum,
9908                frame_type: FrameType::Frame,
9909                content_size: 0,
9910                dict_id: 0,
9911                block_checksum_flag: BlockChecksum::NoBlockChecksum,
9912            };
9913            let mut consumed = frame.len();
9914            let info_code = LZ4F_getFrameInfo(dctx, &mut info, frame.as_ptr(), &mut consumed);
9915            assert_eq!(LZ4F_isError(info_code), 0);
9916            assert!(consumed > 0);
9917            assert_ne!(LZ4F_freeDecompressionContext(dctx), 0);
9918
9919            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9920            let mut output = vec![0u8; input.len()];
9921            let mut src_size = frame.len();
9922            let mut dst_size = output.len();
9923            let code = LZ4F_decompress(
9924                dctx,
9925                output.as_mut_ptr(),
9926                &mut dst_size,
9927                frame.as_ptr(),
9928                &mut src_size,
9929                ptr::null(),
9930            );
9931            assert_eq!(code, 0);
9932            assert_eq!(output, input);
9933            assert_eq!(LZ4F_freeDecompressionContext(dctx), 0);
9934        }
9935    }
9936
9937    #[test]
9938    fn frame_update_compresses_blocks_when_level_is_set() {
9939        unsafe {
9940            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
9941            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
9942            let prefs = LZ4FPreferences {
9943                frame_info: LZ4FFrameInfo {
9944                    block_size_id: BlockSize::Max64KB,
9945                    block_mode: BlockMode::Independent,
9946                    content_checksum_flag: ContentChecksum::NoChecksum,
9947                    frame_type: FrameType::Frame,
9948                    content_size: 0,
9949                    dict_id: 0,
9950                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
9951                },
9952                compression_level: 9,
9953                auto_flush: 0,
9954                favor_dec_speed: 0,
9955                reserved: [0; 3],
9956            };
9957            let input = vec![b'a'; 16 * 1024];
9958            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
9959            let header_len = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
9960            assert!(!LZ4F_isError(header_len).eq(&1));
9961            let update_len = LZ4F_compressUpdate(
9962                cctx,
9963                encoded.as_mut_ptr().add(header_len),
9964                encoded.len() - header_len,
9965                input.as_ptr(),
9966                input.len(),
9967                ptr::null(),
9968            );
9969            assert!(!LZ4F_isError(update_len).eq(&1));
9970            let block_header =
9971                u32::from_le_bytes(encoded[header_len..header_len + 4].try_into().unwrap());
9972            assert_eq!(block_header & 0x8000_0000, 0);
9973            assert!((block_header as usize) < input.len());
9974
9975            let end_len = LZ4F_compressEnd(
9976                cctx,
9977                encoded.as_mut_ptr().add(header_len + update_len),
9978                encoded.len() - header_len - update_len,
9979                ptr::null(),
9980            );
9981            assert!(!LZ4F_isError(end_len).eq(&1));
9982            encoded.truncate(header_len + update_len + end_len);
9983            LZ4F_freeCompressionContext(cctx);
9984
9985            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
9986            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
9987            let mut output = vec![0u8; input.len()];
9988            let mut src_size = encoded.len();
9989            let mut dst_size = output.len();
9990            let code = LZ4F_decompress(
9991                dctx,
9992                output.as_mut_ptr(),
9993                &mut dst_size,
9994                encoded.as_ptr(),
9995                &mut src_size,
9996                ptr::null(),
9997            );
9998            assert!(!LZ4F_isError(code).eq(&1));
9999            assert_eq!(dst_size, input.len());
10000            assert_eq!(output, input);
10001            LZ4F_freeDecompressionContext(dctx);
10002        }
10003    }
10004
10005    #[test]
10006    fn frame_update_multiblock_dst_too_small_returns_error() {
10007        unsafe {
10008            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10009            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10010            let prefs = LZ4FPreferences {
10011                frame_info: LZ4FFrameInfo {
10012                    block_size_id: BlockSize::Max64KB,
10013                    block_mode: BlockMode::Independent,
10014                    content_checksum_flag: ContentChecksum::NoChecksum,
10015                    frame_type: FrameType::Frame,
10016                    content_size: 0,
10017                    dict_id: 0,
10018                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10019                },
10020                compression_level: 0,
10021                auto_flush: 0,
10022                favor_dec_speed: 0,
10023                reserved: [0; 3],
10024            };
10025            let input = vec![0x5au8; 128 * 1024];
10026            let mut output = vec![0u8; 16];
10027            assert_eq!(
10028                LZ4F_compressBegin(cctx, output.as_mut_ptr(), output.len(), &prefs),
10029                7
10030            );
10031            let code = LZ4F_compressUpdate(
10032                cctx,
10033                output.as_mut_ptr(),
10034                output.len(),
10035                input.as_ptr(),
10036                input.len(),
10037                ptr::null(),
10038            );
10039            assert_eq!(code, ERROR_DST_TOO_SMALL);
10040            LZ4F_freeCompressionContext(cctx);
10041        }
10042    }
10043
10044    #[test]
10045    fn frame_update_buffers_partial_block_until_flush_or_end() {
10046        unsafe {
10047            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10048            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10049            let prefs = LZ4FPreferences {
10050                frame_info: LZ4FFrameInfo {
10051                    block_size_id: BlockSize::Max64KB,
10052                    block_mode: BlockMode::Independent,
10053                    content_checksum_flag: ContentChecksum::NoChecksum,
10054                    frame_type: FrameType::Frame,
10055                    content_size: 0,
10056                    dict_id: 0,
10057                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10058                },
10059                compression_level: 0,
10060                auto_flush: 0,
10061                favor_dec_speed: 0,
10062                reserved: [0; 3],
10063            };
10064            let input = b"buffered partial frame block";
10065            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
10066            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10067            let update_len = LZ4F_compressUpdate(
10068                cctx,
10069                encoded.as_mut_ptr().add(pos),
10070                encoded.len() - pos,
10071                input.as_ptr(),
10072                input.len(),
10073                ptr::null(),
10074            );
10075            assert_eq!(update_len, 0);
10076            let flush_len = LZ4F_flush(
10077                cctx,
10078                encoded.as_mut_ptr().add(pos),
10079                encoded.len() - pos,
10080                ptr::null(),
10081            );
10082            assert_eq!(LZ4F_isError(flush_len), 0);
10083            assert!(flush_len > 4);
10084            pos += flush_len;
10085            let end_len = LZ4F_compressEnd(
10086                cctx,
10087                encoded.as_mut_ptr().add(pos),
10088                encoded.len() - pos,
10089                ptr::null(),
10090            );
10091            assert_eq!(end_len, 4);
10092            pos += end_len;
10093            encoded.truncate(pos);
10094            LZ4F_freeCompressionContext(cctx);
10095
10096            let decoded = decode_frame_once(&encoded, input.len());
10097            assert_eq!(decoded, input);
10098        }
10099    }
10100
10101    #[test]
10102    fn frame_compress_end_flushes_buffered_partial_block() {
10103        unsafe {
10104            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10105            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10106            let prefs = LZ4FPreferences {
10107                frame_info: LZ4FFrameInfo {
10108                    block_size_id: BlockSize::Max64KB,
10109                    block_mode: BlockMode::Independent,
10110                    content_checksum_flag: ContentChecksum::NoChecksum,
10111                    frame_type: FrameType::Frame,
10112                    content_size: 0,
10113                    dict_id: 0,
10114                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10115                },
10116                compression_level: 0,
10117                auto_flush: 0,
10118                favor_dec_speed: 0,
10119                reserved: [0; 3],
10120            };
10121            let input = b"finish flushes this tail";
10122            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
10123            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10124            assert_eq!(
10125                LZ4F_compressUpdate(
10126                    cctx,
10127                    encoded.as_mut_ptr().add(pos),
10128                    encoded.len() - pos,
10129                    input.as_ptr(),
10130                    input.len(),
10131                    ptr::null(),
10132                ),
10133                0
10134            );
10135            let end_len = LZ4F_compressEnd(
10136                cctx,
10137                encoded.as_mut_ptr().add(pos),
10138                encoded.len() - pos,
10139                ptr::null(),
10140            );
10141            assert_eq!(LZ4F_isError(end_len), 0);
10142            assert!(end_len > 4);
10143            pos += end_len;
10144            encoded.truncate(pos);
10145            LZ4F_freeCompressionContext(cctx);
10146
10147            let decoded = decode_frame_once(&encoded, input.len());
10148            assert_eq!(decoded, input);
10149        }
10150    }
10151
10152    #[test]
10153    fn frame_compress_end_rejects_declared_content_size_mismatch() {
10154        unsafe {
10155            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10156            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10157            let input = b"short";
10158            let prefs = LZ4FPreferences {
10159                frame_info: LZ4FFrameInfo {
10160                    block_size_id: BlockSize::Max64KB,
10161                    block_mode: BlockMode::Independent,
10162                    content_checksum_flag: ContentChecksum::NoChecksum,
10163                    frame_type: FrameType::Frame,
10164                    content_size: input.len() as u64 + 1,
10165                    dict_id: 0,
10166                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10167                },
10168                compression_level: 0,
10169                auto_flush: 0,
10170                favor_dec_speed: 0,
10171                reserved: [0; 3],
10172            };
10173            let mut output = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
10174            let mut pos = LZ4F_compressBegin(cctx, output.as_mut_ptr(), output.len(), &prefs);
10175            assert_eq!(LZ4F_isError(pos), 0);
10176            let update_len = LZ4F_compressUpdate(
10177                cctx,
10178                output.as_mut_ptr().add(pos),
10179                output.len() - pos,
10180                input.as_ptr(),
10181                input.len(),
10182                ptr::null(),
10183            );
10184            assert_eq!(update_len, 0);
10185            pos += update_len;
10186            assert_eq!(
10187                LZ4F_compressEnd(
10188                    cctx,
10189                    output.as_mut_ptr().add(pos),
10190                    output.len() - pos,
10191                    ptr::null(),
10192                ),
10193                ERROR_FRAME_SIZE_WRONG
10194            );
10195            LZ4F_freeCompressionContext(cctx);
10196        }
10197    }
10198
10199    #[test]
10200    fn frame_compress_frame_corrects_nonzero_declared_content_size() {
10201        unsafe {
10202            let input = b"actual content size";
10203            let mut prefs = LZ4FPreferences {
10204                frame_info: LZ4FFrameInfo {
10205                    block_size_id: BlockSize::Max64KB,
10206                    block_mode: BlockMode::Independent,
10207                    content_checksum_flag: ContentChecksum::NoChecksum,
10208                    frame_type: FrameType::Frame,
10209                    content_size: input.len() as u64 + 100,
10210                    dict_id: 0,
10211                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10212                },
10213                compression_level: 0,
10214                auto_flush: 0,
10215                favor_dec_speed: 0,
10216                reserved: [0; 3],
10217            };
10218            let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
10219            let encoded_len = LZ4F_compressFrame(
10220                encoded.as_mut_ptr() as *mut c_void,
10221                encoded.len(),
10222                input.as_ptr() as *const c_void,
10223                input.len(),
10224                &prefs,
10225            );
10226            assert_eq!(LZ4F_isError(encoded_len), 0);
10227            encoded.truncate(encoded_len);
10228            assert_eq!(decode_frame_once(&encoded, input.len()), input);
10229
10230            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10231            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10232            prefs.frame_info.content_size = 0;
10233            let mut info = prefs.frame_info;
10234            let mut src_size = encoded.len();
10235            assert_eq!(
10236                LZ4F_getFrameInfo(dctx, &mut info, encoded.as_ptr(), &mut src_size),
10237                0
10238            );
10239            assert_eq!(info.content_size, input.len() as u64);
10240            LZ4F_freeDecompressionContext(dctx);
10241        }
10242    }
10243
10244    #[test]
10245    fn frame_linked_blocks_use_previous_block_history() {
10246        unsafe {
10247            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10248            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10249            let prefs = LZ4FPreferences {
10250                frame_info: LZ4FFrameInfo {
10251                    block_size_id: BlockSize::Max64KB,
10252                    block_mode: BlockMode::Linked,
10253                    content_checksum_flag: ContentChecksum::NoChecksum,
10254                    frame_type: FrameType::Frame,
10255                    content_size: 0,
10256                    dict_id: 0,
10257                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10258                },
10259                compression_level: 9,
10260                auto_flush: 0,
10261                favor_dec_speed: 0,
10262                reserved: [0; 3],
10263            };
10264            let first = b"abcdefghijklmnop";
10265            let second = b"abcdefghijklmnop";
10266            let mut encoded = vec![0u8; 256];
10267            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10268
10269            let mut dctx_info = LZ4FDecompressionContext(ptr::null_mut());
10270            assert_eq!(
10271                LZ4F_createDecompressionContext(&mut dctx_info, LZ4F_VERSION),
10272                0
10273            );
10274            let mut info = LZ4FFrameInfo {
10275                block_size_id: BlockSize::Default,
10276                block_mode: BlockMode::Independent,
10277                content_checksum_flag: ContentChecksum::NoChecksum,
10278                frame_type: FrameType::Frame,
10279                content_size: 0,
10280                dict_id: 0,
10281                block_checksum_flag: BlockChecksum::NoBlockChecksum,
10282            };
10283            let mut header_size = pos;
10284            assert_eq!(
10285                LZ4F_getFrameInfo(dctx_info, &mut info, encoded.as_ptr(), &mut header_size),
10286                0
10287            );
10288            assert!(matches!(info.block_mode, BlockMode::Linked));
10289            LZ4F_freeDecompressionContext(dctx_info);
10290
10291            pos += LZ4F_compressUpdate(
10292                cctx,
10293                encoded.as_mut_ptr().add(pos),
10294                encoded.len() - pos,
10295                first.as_ptr(),
10296                first.len(),
10297                ptr::null(),
10298            );
10299            let second_block_at = pos;
10300            let second_len = LZ4F_compressUpdate(
10301                cctx,
10302                encoded.as_mut_ptr().add(pos),
10303                encoded.len() - pos,
10304                second.as_ptr(),
10305                second.len(),
10306                ptr::null(),
10307            );
10308            pos += second_len;
10309            let second_header = u32::from_le_bytes(
10310                encoded[second_block_at..second_block_at + 4]
10311                    .try_into()
10312                    .unwrap(),
10313            );
10314            assert_eq!(second_header & 0x8000_0000, 0);
10315            assert!((second_header as usize) < second.len() + 1);
10316            pos += LZ4F_compressEnd(
10317                cctx,
10318                encoded.as_mut_ptr().add(pos),
10319                encoded.len() - pos,
10320                ptr::null(),
10321            );
10322            encoded.truncate(pos);
10323            LZ4F_freeCompressionContext(cctx);
10324
10325            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10326            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10327            let mut output = vec![0u8; first.len() + second.len()];
10328            let mut src_offset = 0usize;
10329            let mut dst_offset = 0usize;
10330            loop {
10331                let mut src_size = encoded.len() - src_offset;
10332                let mut dst_size = output.len() - dst_offset;
10333                let code = LZ4F_decompress(
10334                    dctx,
10335                    output[dst_offset..].as_mut_ptr(),
10336                    &mut dst_size,
10337                    encoded.as_ptr().add(src_offset),
10338                    &mut src_size,
10339                    ptr::null(),
10340                );
10341                assert_eq!(LZ4F_isError(code), 0);
10342                src_offset += src_size;
10343                dst_offset += dst_size;
10344                if code == 0 {
10345                    break;
10346                }
10347            }
10348            assert_eq!(dst_offset, output.len());
10349            assert_eq!(&output[..first.len()], first);
10350            assert_eq!(&output[first.len()..], second);
10351            LZ4F_freeDecompressionContext(dctx);
10352        }
10353    }
10354
10355    #[test]
10356    fn frame_decompress_rejects_bad_checksums() {
10357        unsafe {
10358            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10359            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10360            let prefs = LZ4FPreferences {
10361                frame_info: LZ4FFrameInfo {
10362                    block_size_id: BlockSize::Max64KB,
10363                    block_mode: BlockMode::Independent,
10364                    content_checksum_flag: ContentChecksum::ChecksumEnabled,
10365                    frame_type: FrameType::Frame,
10366                    content_size: 0,
10367                    dict_id: 0,
10368                    block_checksum_flag: BlockChecksum::BlockChecksumEnabled,
10369                },
10370                compression_level: 9,
10371                auto_flush: 0,
10372                favor_dec_speed: 0,
10373                reserved: [0; 3],
10374            };
10375            let input = b"checksum data ".repeat(1024);
10376            let mut encoded = vec![0u8; LZ4F_compressBound(input.len(), &prefs) + 32];
10377            let header_len = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10378            let update_len = LZ4F_compressUpdate(
10379                cctx,
10380                encoded.as_mut_ptr().add(header_len),
10381                encoded.len() - header_len,
10382                input.as_ptr(),
10383                input.len(),
10384                ptr::null(),
10385            );
10386            let end_len = LZ4F_compressEnd(
10387                cctx,
10388                encoded.as_mut_ptr().add(header_len + update_len),
10389                encoded.len() - header_len - update_len,
10390                ptr::null(),
10391            );
10392            encoded.truncate(header_len + update_len + end_len);
10393            LZ4F_freeCompressionContext(cctx);
10394
10395            let mut bad_block = encoded.clone();
10396            let block_len =
10397                (u32::from_le_bytes(bad_block[header_len..header_len + 4].try_into().unwrap())
10398                    & 0x7FFF_FFFF) as usize;
10399            bad_block[header_len + 4 + block_len] ^= 0x80;
10400            assert_corrupt_frame_fails("block checksum", &bad_block, input.len());
10401            assert_corrupt_frame_decodes_with_skip_checksums("block checksum", &bad_block, &input);
10402            assert_corrupt_frame_decodes_with_sticky_skip_checksums(
10403                "block checksum",
10404                &bad_block,
10405                &input,
10406                header_len,
10407            );
10408
10409            let mut bad_content = encoded;
10410            let last = bad_content.len() - 1;
10411            bad_content[last] ^= 0x80;
10412            assert_corrupt_frame_fails("content checksum", &bad_content, input.len());
10413            assert_corrupt_frame_decodes_with_skip_checksums(
10414                "content checksum",
10415                &bad_content,
10416                &input,
10417            );
10418            assert_corrupt_frame_decodes_with_sticky_skip_checksums(
10419                "content checksum",
10420                &bad_content,
10421                &input,
10422                header_len,
10423            );
10424        }
10425    }
10426
10427    #[test]
10428    fn frame_decompress_raw_block_consumes_only_output_capacity() {
10429        unsafe {
10430            let input = b"raw block incremental payload";
10431            let mut frame = frame_header_for_test(
10432                ContentChecksum::NoChecksum,
10433                BlockChecksum::NoBlockChecksum,
10434                0,
10435            );
10436            frame.extend_from_slice(&((input.len() as u32) | 0x8000_0000).to_le_bytes());
10437            frame.extend_from_slice(input);
10438            frame.extend_from_slice(&0u32.to_le_bytes());
10439
10440            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10441            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10442            let mut info = LZ4FFrameInfo {
10443                block_size_id: BlockSize::Default,
10444                block_mode: BlockMode::Linked,
10445                content_checksum_flag: ContentChecksum::NoChecksum,
10446                frame_type: FrameType::Frame,
10447                content_size: 0,
10448                dict_id: 0,
10449                block_checksum_flag: BlockChecksum::NoBlockChecksum,
10450            };
10451            let mut header_size = frame.len();
10452            assert_eq!(
10453                LZ4F_getFrameInfo(dctx, &mut info, frame.as_ptr(), &mut header_size),
10454                0
10455            );
10456
10457            let payload = &frame[header_size..];
10458            let mut first = [0u8; 5];
10459            let mut src_size = payload.len();
10460            let mut dst_size = first.len();
10461            let hint = LZ4F_decompress(
10462                dctx,
10463                first.as_mut_ptr(),
10464                &mut dst_size,
10465                payload.as_ptr(),
10466                &mut src_size,
10467                ptr::null(),
10468            );
10469            assert_eq!(LZ4F_isError(hint), 0);
10470            assert_eq!(dst_size, first.len());
10471            assert_eq!(src_size, 4 + first.len());
10472            assert_eq!(&first, &input[..first.len()]);
10473
10474            let rest_payload = &payload[src_size..];
10475            let mut rest = vec![0u8; input.len() - first.len()];
10476            let mut rest_src_size = rest_payload.len();
10477            let mut rest_dst_size = rest.len();
10478            let code = LZ4F_decompress(
10479                dctx,
10480                rest.as_mut_ptr(),
10481                &mut rest_dst_size,
10482                rest_payload.as_ptr(),
10483                &mut rest_src_size,
10484                ptr::null(),
10485            );
10486            assert_eq!(code, 0);
10487            assert_eq!(rest_src_size, rest_payload.len());
10488            assert_eq!(rest_dst_size, rest.len());
10489            assert_eq!(&rest, &input[first.len()..]);
10490            LZ4F_freeDecompressionContext(dctx);
10491        }
10492    }
10493
10494    #[test]
10495    fn frame_decompress_rejects_bad_header_checksum() {
10496        unsafe {
10497            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10498            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10499            let prefs = LZ4FPreferences {
10500                frame_info: LZ4FFrameInfo {
10501                    block_size_id: BlockSize::Max64KB,
10502                    block_mode: BlockMode::Independent,
10503                    content_checksum_flag: ContentChecksum::NoChecksum,
10504                    frame_type: FrameType::Frame,
10505                    content_size: 0,
10506                    dict_id: 0,
10507                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10508                },
10509                compression_level: 0,
10510                auto_flush: 0,
10511                favor_dec_speed: 0,
10512                reserved: [0; 3],
10513            };
10514            let input = b"header checksum";
10515            let mut encoded = vec![0u8; 128];
10516            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10517            encoded[pos - 1] ^= 0x80;
10518            pos += LZ4F_compressUpdate(
10519                cctx,
10520                encoded.as_mut_ptr().add(pos),
10521                encoded.len() - pos,
10522                input.as_ptr(),
10523                input.len(),
10524                ptr::null(),
10525            );
10526            pos += LZ4F_compressEnd(
10527                cctx,
10528                encoded.as_mut_ptr().add(pos),
10529                encoded.len() - pos,
10530                ptr::null(),
10531            );
10532            encoded.truncate(pos);
10533            LZ4F_freeCompressionContext(cctx);
10534
10535            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10536            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10537            let mut output = vec![0u8; input.len()];
10538            let mut src_size = encoded.len();
10539            let mut dst_size = output.len();
10540            let code = LZ4F_decompress(
10541                dctx,
10542                output.as_mut_ptr(),
10543                &mut dst_size,
10544                encoded.as_ptr(),
10545                &mut src_size,
10546                ptr::null(),
10547            );
10548            assert_eq!(code, ERROR_HEADER_CHECKSUM_INVALID);
10549            LZ4F_freeDecompressionContext(dctx);
10550        }
10551    }
10552
10553    #[test]
10554    fn frame_decompress_rejects_malformed_compressed_block() {
10555        unsafe {
10556            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10557            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10558            let prefs = LZ4FPreferences {
10559                frame_info: LZ4FFrameInfo {
10560                    block_size_id: BlockSize::Max64KB,
10561                    block_mode: BlockMode::Independent,
10562                    content_checksum_flag: ContentChecksum::NoChecksum,
10563                    frame_type: FrameType::Frame,
10564                    content_size: 0,
10565                    dict_id: 0,
10566                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10567                },
10568                compression_level: 0,
10569                auto_flush: 0,
10570                favor_dec_speed: 0,
10571                reserved: [0; 3],
10572            };
10573            let mut encoded = vec![0u8; 32];
10574            let header_len = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10575            assert_eq!(LZ4F_isError(header_len), 0);
10576            encoded.truncate(header_len);
10577            LZ4F_freeCompressionContext(cctx);
10578
10579            let cases = [
10580                ("offset past output", [0x00, 0x01, 0x00]),
10581                ("zero offset", [0x00, 0x00, 0x00]),
10582            ];
10583            for (case, payload) in cases {
10584                let mut frame = encoded.clone();
10585                frame.extend_from_slice(&(payload.len() as u32).to_le_bytes());
10586                frame.extend_from_slice(&payload);
10587
10588                let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10589                assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10590                let mut output = vec![0u8; 64 * 1024];
10591                let mut src_size = frame.len();
10592                let mut dst_size = output.len();
10593                let code = LZ4F_decompress(
10594                    dctx,
10595                    output.as_mut_ptr(),
10596                    &mut dst_size,
10597                    frame.as_ptr(),
10598                    &mut src_size,
10599                    ptr::null(),
10600                );
10601                assert_eq!(code, ERROR_DECOMPRESSION_FAILED, "{case}");
10602                LZ4F_freeDecompressionContext(dctx);
10603            }
10604        }
10605    }
10606
10607    #[test]
10608    fn frame_decompress_reports_hints_for_truncated_frame_parts() {
10609        unsafe {
10610            let no_checksum = frame_header_for_test(
10611                ContentChecksum::NoChecksum,
10612                BlockChecksum::NoBlockChecksum,
10613                0,
10614            );
10615            assert_incomplete_frame_hint(
10616                "partial block header",
10617                {
10618                    let mut frame = no_checksum.clone();
10619                    frame.extend_from_slice(&[0x05, 0x00]);
10620                    frame
10621                },
10622                2,
10623                0,
10624            );
10625            assert_incomplete_frame_hint(
10626                "partial raw block payload",
10627                {
10628                    let mut frame = no_checksum.clone();
10629                    frame.extend_from_slice(&(0x8000_0000u32 | 5).to_le_bytes());
10630                    frame.extend_from_slice(b"ab");
10631                    frame
10632                },
10633                7,
10634                2,
10635            );
10636
10637            let with_block_checksum = frame_header_for_test(
10638                ContentChecksum::NoChecksum,
10639                BlockChecksum::BlockChecksumEnabled,
10640                0,
10641            );
10642            assert_incomplete_frame_hint(
10643                "missing block checksum",
10644                {
10645                    let mut frame = with_block_checksum;
10646                    frame.extend_from_slice(&(0x8000_0000u32 | 3).to_le_bytes());
10647                    frame.extend_from_slice(b"abc");
10648                    frame
10649                },
10650                4,
10651                3,
10652            );
10653
10654            let with_content_checksum = frame_header_for_test(
10655                ContentChecksum::ChecksumEnabled,
10656                BlockChecksum::NoBlockChecksum,
10657                0,
10658            );
10659            assert_incomplete_frame_hint(
10660                "missing content checksum trailer",
10661                {
10662                    let mut frame = with_content_checksum;
10663                    frame.extend_from_slice(&(0x8000_0000u32 | 3).to_le_bytes());
10664                    frame.extend_from_slice(b"abc");
10665                    frame.extend_from_slice(&0u32.to_le_bytes());
10666                    frame
10667                },
10668                4,
10669                3,
10670            );
10671        }
10672    }
10673
10674    #[test]
10675    fn frame_decompress_rejects_content_size_mismatch() {
10676        unsafe {
10677            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10678            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10679            let input = b"wrong content size";
10680            let prefs = LZ4FPreferences {
10681                frame_info: LZ4FFrameInfo {
10682                    block_size_id: BlockSize::Max64KB,
10683                    block_mode: BlockMode::Independent,
10684                    content_checksum_flag: ContentChecksum::NoChecksum,
10685                    frame_type: FrameType::Frame,
10686                    content_size: input.len() as u64 + 1,
10687                    dict_id: 0,
10688                    block_checksum_flag: BlockChecksum::NoBlockChecksum,
10689                },
10690                compression_level: 0,
10691                auto_flush: 0,
10692                favor_dec_speed: 0,
10693                reserved: [0; 3],
10694            };
10695            let mut encoded = vec![0u8; 128];
10696            let mut pos = LZ4F_compressBegin(cctx, encoded.as_mut_ptr(), encoded.len(), &prefs);
10697            assert_eq!(LZ4F_isError(pos), 0);
10698            encoded[pos..pos + 4]
10699                .copy_from_slice(&((input.len() as u32) | 0x8000_0000).to_le_bytes());
10700            pos += 4;
10701            encoded[pos..pos + input.len()].copy_from_slice(input);
10702            pos += input.len();
10703            encoded[pos..pos + 4].copy_from_slice(&0u32.to_le_bytes());
10704            pos += 4;
10705            encoded.truncate(pos);
10706            LZ4F_freeCompressionContext(cctx);
10707
10708            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10709            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10710            let mut output = vec![0u8; input.len()];
10711            let mut src_size = encoded.len();
10712            let mut dst_size = output.len();
10713            let code = LZ4F_decompress(
10714                dctx,
10715                output.as_mut_ptr(),
10716                &mut dst_size,
10717                encoded.as_ptr(),
10718                &mut src_size,
10719                ptr::null(),
10720            );
10721            assert_eq!(code, ERROR_FRAME_SIZE_WRONG);
10722            LZ4F_freeDecompressionContext(dctx);
10723        }
10724    }
10725
10726    fn assert_corrupt_frame_fails(kind: &str, encoded: &[u8], output_len: usize) {
10727        unsafe {
10728            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10729            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10730            let mut output = vec![0u8; output_len];
10731            let mut offset = 0usize;
10732            for _ in 0..8 {
10733                let mut src_size = encoded.len() - offset;
10734                let src_ptr = if src_size == 0 {
10735                    ptr::null()
10736                } else {
10737                    encoded.as_ptr().add(offset)
10738                };
10739                let mut dst_size = output.len();
10740                let code = LZ4F_decompress(
10741                    dctx,
10742                    output.as_mut_ptr(),
10743                    &mut dst_size,
10744                    src_ptr,
10745                    &mut src_size,
10746                    ptr::null(),
10747                );
10748                if code == ERROR_BLOCK_CHECKSUM_INVALID || code == ERROR_CHECKSUM_INVALID {
10749                    LZ4F_freeDecompressionContext(dctx);
10750                    return;
10751                }
10752                assert_eq!(LZ4F_isError(code), 0);
10753                assert_ne!(code, 0, "{kind} corrupt frame decoded successfully");
10754                offset += src_size;
10755            }
10756            panic!("{kind} corrupt frame did not report checksum failure");
10757        }
10758    }
10759
10760    fn assert_corrupt_frame_decodes_with_skip_checksums(kind: &str, encoded: &[u8], input: &[u8]) {
10761        unsafe {
10762            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10763            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10764            let options = LZ4FDecompressOptions {
10765                stable_dst: 0,
10766                skipChecksums: 1,
10767                reserved: [0; 2],
10768            };
10769            let mut output = vec![0u8; input.len()];
10770            let mut src_size = encoded.len();
10771            let mut dst_size = output.len();
10772            let code = LZ4F_decompress(
10773                dctx,
10774                output.as_mut_ptr(),
10775                &mut dst_size,
10776                encoded.as_ptr(),
10777                &mut src_size,
10778                &options,
10779            );
10780            assert_eq!(code, 0, "{kind}");
10781            assert_eq!(src_size, encoded.len(), "{kind}");
10782            assert_eq!(dst_size, input.len(), "{kind}");
10783            assert_eq!(output, input, "{kind}");
10784            LZ4F_freeDecompressionContext(dctx);
10785        }
10786    }
10787
10788    fn assert_corrupt_frame_decodes_with_sticky_skip_checksums(
10789        kind: &str,
10790        encoded: &[u8],
10791        input: &[u8],
10792        header_len: usize,
10793    ) {
10794        unsafe {
10795            let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10796            assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10797            let options = LZ4FDecompressOptions {
10798                stable_dst: 0,
10799                skipChecksums: 1,
10800                reserved: [0; 2],
10801            };
10802            let mut src_size = header_len;
10803            let mut dst_size = 0usize;
10804            let code = LZ4F_decompress(
10805                dctx,
10806                ptr::null_mut(),
10807                &mut dst_size,
10808                encoded.as_ptr(),
10809                &mut src_size,
10810                &options,
10811            );
10812            assert_eq!(LZ4F_isError(code), 0, "{kind}");
10813            assert_eq!(src_size, header_len, "{kind}");
10814            assert_eq!(dst_size, 0, "{kind}");
10815
10816            let mut output = vec![0u8; input.len()];
10817            src_size = encoded.len() - header_len;
10818            dst_size = output.len();
10819            let code = LZ4F_decompress(
10820                dctx,
10821                output.as_mut_ptr(),
10822                &mut dst_size,
10823                encoded.as_ptr().add(header_len),
10824                &mut src_size,
10825                ptr::null(),
10826            );
10827            assert_eq!(code, 0, "{kind}");
10828            assert_eq!(src_size, encoded.len() - header_len, "{kind}");
10829            assert_eq!(dst_size, input.len(), "{kind}");
10830            assert_eq!(output, input, "{kind}");
10831            LZ4F_freeDecompressionContext(dctx);
10832        }
10833    }
10834
10835    unsafe fn frame_header_for_test(
10836        content_checksum: ContentChecksum,
10837        block_checksum: BlockChecksum,
10838        content_size: u64,
10839    ) -> Vec<u8> {
10840        let mut cctx = LZ4FCompressionContext(ptr::null_mut());
10841        assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
10842        let prefs = LZ4FPreferences {
10843            frame_info: LZ4FFrameInfo {
10844                block_size_id: BlockSize::Max64KB,
10845                block_mode: BlockMode::Independent,
10846                content_checksum_flag: content_checksum,
10847                frame_type: FrameType::Frame,
10848                content_size,
10849                dict_id: 0,
10850                block_checksum_flag: block_checksum,
10851            },
10852            compression_level: 0,
10853            auto_flush: 0,
10854            favor_dec_speed: 0,
10855            reserved: [0; 3],
10856        };
10857        let mut header = vec![0u8; 32];
10858        let header_len = LZ4F_compressBegin(cctx, header.as_mut_ptr(), header.len(), &prefs);
10859        assert_eq!(LZ4F_isError(header_len), 0);
10860        LZ4F_freeCompressionContext(cctx);
10861        header.truncate(header_len);
10862        header
10863    }
10864
10865    unsafe fn assert_incomplete_frame_hint(
10866        case: &str,
10867        frame: Vec<u8>,
10868        expected_hint: usize,
10869        expected_output: usize,
10870    ) {
10871        let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10872        assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10873        let mut output = vec![0u8; 64 * 1024];
10874        let mut src_size = frame.len();
10875        let mut dst_size = output.len();
10876        let code = LZ4F_decompress(
10877            dctx,
10878            output.as_mut_ptr(),
10879            &mut dst_size,
10880            frame.as_ptr(),
10881            &mut src_size,
10882            ptr::null(),
10883        );
10884        assert_eq!(LZ4F_isError(code), 0, "{case}");
10885        assert_eq!(code, expected_hint, "{case}");
10886        assert_eq!(dst_size, expected_output, "{case}");
10887        assert_eq!(src_size, frame.len(), "{case}");
10888        LZ4F_freeDecompressionContext(dctx);
10889    }
10890
10891    unsafe fn decode_frame_once(frame: &[u8], output_len: usize) -> Vec<u8> {
10892        let mut dctx = LZ4FDecompressionContext(ptr::null_mut());
10893        assert_eq!(LZ4F_createDecompressionContext(&mut dctx, LZ4F_VERSION), 0);
10894        let mut output = vec![0u8; output_len + 16];
10895        let mut src_offset = 0usize;
10896        let mut dst_offset = 0usize;
10897        loop {
10898            let mut src_size = frame.len() - src_offset;
10899            let mut dst_size = output.len() - dst_offset;
10900            let code = LZ4F_decompress(
10901                dctx,
10902                output.as_mut_ptr().add(dst_offset),
10903                &mut dst_size,
10904                frame.as_ptr().add(src_offset),
10905                &mut src_size,
10906                ptr::null(),
10907            );
10908            assert_eq!(LZ4F_isError(code), 0);
10909            src_offset += src_size;
10910            dst_offset += dst_size;
10911            if code == 0 {
10912                break;
10913            }
10914        }
10915        assert_eq!(src_offset, frame.len());
10916        output.truncate(dst_offset);
10917        LZ4F_freeDecompressionContext(dctx);
10918        output
10919    }
10920
10921    #[test]
10922    fn hc_round_trip_and_improves_repetitive_block() {
10923        let mut input = Vec::new();
10924        for _ in 0..4096 {
10925            input.extend_from_slice(b"the quick brown fox jumps over the lazy dog. ");
10926        }
10927
10928        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
10929        let mut fast = vec![0u8; bound];
10930        let fast_len = unsafe {
10931            LZ4_compress_default(
10932                input.as_ptr() as *const c_char,
10933                fast.as_mut_ptr() as *mut c_char,
10934                input.len() as c_int,
10935                fast.len() as c_int,
10936            )
10937        };
10938        assert!(fast_len > 0);
10939
10940        let mut hc = vec![0u8; bound];
10941        let hc_len = unsafe {
10942            LZ4_compress_HC(
10943                input.as_ptr() as *const c_char,
10944                hc.as_mut_ptr() as *mut c_char,
10945                input.len() as c_int,
10946                hc.len() as c_int,
10947                9,
10948            )
10949        };
10950        assert!(hc_len > 0);
10951        assert!(hc_len <= fast_len);
10952
10953        let mut output = vec![0u8; input.len()];
10954        let output_len = unsafe {
10955            LZ4_decompress_safe(
10956                hc.as_ptr() as *const c_char,
10957                output.as_mut_ptr() as *mut c_char,
10958                hc_len,
10959                output.len() as c_int,
10960            )
10961        };
10962        assert_eq!(output_len as usize, input.len());
10963        assert_eq!(output, input);
10964    }
10965
10966    #[test]
10967    fn hc_levels_round_trip_varied_inputs() {
10968        let mut inputs = Vec::new();
10969        inputs.push((0..4096).map(|n| (n & 0xff) as u8).collect::<Vec<_>>());
10970        inputs.push(vec![b'a'; 128 * 1024]);
10971        let mut patterned = Vec::new();
10972        for n in 0..32 * 1024 {
10973            patterned.push(if n % 97 < 64 {
10974                b"ACGT"[(n / 3) % 4]
10975            } else {
10976                (n & 0xff) as u8
10977            });
10978        }
10979        inputs.push(patterned);
10980
10981        for input in inputs {
10982            let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
10983            for level in 1..=12 {
10984                let mut compressed = vec![0u8; bound];
10985                let compressed_len = unsafe {
10986                    LZ4_compress_HC(
10987                        input.as_ptr() as *const c_char,
10988                        compressed.as_mut_ptr() as *mut c_char,
10989                        input.len() as c_int,
10990                        compressed.len() as c_int,
10991                        level,
10992                    )
10993                };
10994                assert!(compressed_len > 0, "level {level}");
10995
10996                let mut output = vec![0u8; input.len()];
10997                let output_len = unsafe {
10998                    LZ4_decompress_safe(
10999                        compressed.as_ptr() as *const c_char,
11000                        output.as_mut_ptr() as *mut c_char,
11001                        compressed_len,
11002                        output.len() as c_int,
11003                    )
11004                };
11005                assert_eq!(output_len as usize, input.len(), "level {level}");
11006                assert_eq!(output, input, "level {level}");
11007            }
11008        }
11009    }
11010
11011    #[test]
11012    fn hc_matches_upstream_bytes_for_representative_levels() {
11013        let cases = [
11014            (
11015                1,
11016                patterned_hc_input(128),
11017                "ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000d4e00503334353637",
11018            ),
11019            (
11020                2,
11021                patterned_hc_input(1024),
11022                "ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000f4e00320f9c0000144662000d1a000fd00034144562000f4e00320f520100144462000d1a000f86013403e3012f43444e00320f86010003e3012d42431a000f860134144162000f4e00320f860100144762000d1a000f860134144662000f4e00320f86010003e3012d45461a000f86010450666768696a",
11023            ),
11024            (
11025                12,
11026                b"the quick brown fox jumps over the lazy dog. "
11027                    .iter()
11028                    .copied()
11029                    .cycle()
11030                    .take(4096)
11031                    .collect::<Vec<_>>(),
11032                "f01074686520717569636b2062726f776e20666f78206a756d7073206f766572201f00af6c617a7920646f672e202d00ffffffffffffffffffffffffffffffca506f672e2074",
11033            ),
11034            (
11035                9,
11036                patterned_hc_input(128),
11037                "ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000d1a00503334353637",
11038            ),
11039            (
11040                10,
11041                patterned_hc_input(1024),
11042                "ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000f4e00320f1a0000144662000fb60039081a00144562000f1e0140011a00144462000f1e0140011a00144362000f1e0140011a00144262000f1e0140011a0005a7020fa4024235333435a7020fa40242356d6e6fa7020fa4024235666768a7020f34001550666768696a",
11043            ),
11044        ];
11045
11046        for (level, input, expected_hex) in cases {
11047            let expected = decode_hex(expected_hex);
11048            let mut compressed =
11049                vec![0u8; unsafe { LZ4_compressBound(input.len() as c_int) } as usize];
11050            let compressed_len = unsafe {
11051                LZ4_compress_HC(
11052                    input.as_ptr() as *const c_char,
11053                    compressed.as_mut_ptr() as *mut c_char,
11054                    input.len() as c_int,
11055                    compressed.len() as c_int,
11056                    level,
11057                )
11058            };
11059            assert_eq!(compressed_len as usize, expected.len(), "level {level}");
11060            assert_eq!(
11061                &compressed[..compressed_len as usize],
11062                &expected,
11063                "level {level}"
11064            );
11065        }
11066    }
11067
11068    #[test]
11069    fn fast_matches_upstream_bytes_for_representative_blocks() {
11070        let cases = [
11071            (
11072                1,
11073                b"the quick brown fox jumps over the lazy dog. "
11074                    .iter()
11075                    .copied()
11076                    .cycle()
11077                    .take(4096)
11078                    .collect::<Vec<_>>(),
11079                "f01074686520717569636b2062726f776e20666f78206a756d7073206f766572201f00916c617a7920646f672e0e000f2d00ffffffffffffffffffffffffffffffc6506f672e2074",
11080            ),
11081            (
11082                1,
11083                patterned_hc_input(512),
11084                "ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000f4e00320f9c000000bd0001c4000fb60039086800011f010062000f1e01400168000281013f4344456c012d014e000fba010000bd0001880109d401506e6f703031",
11085            ),
11086            (
11087                4,
11088                patterned_hc_input(512),
11089                "ff2e4142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768696a6b6c6d6e6f7030313233343536373839616263646566676869340011144762000f3400180f68001a144662000fb60039088200144562000f1e0140016800144462000f6c012d014e000fa0010003e301294344d401506e6f703031",
11090            ),
11091        ];
11092
11093        for (acceleration, input, expected_hex) in cases {
11094            let expected = decode_hex(expected_hex);
11095            let mut compressed =
11096                vec![0u8; unsafe { LZ4_compressBound(input.len() as c_int) } as usize];
11097            let compressed_len = unsafe {
11098                LZ4_compress_fast(
11099                    input.as_ptr() as *const c_char,
11100                    compressed.as_mut_ptr() as *mut c_char,
11101                    input.len() as c_int,
11102                    compressed.len() as c_int,
11103                    acceleration,
11104                )
11105            };
11106            assert_eq!(compressed_len as usize, expected.len(), "{acceleration}");
11107            assert_eq!(
11108                &compressed[..compressed_len as usize],
11109                &expected,
11110                "{acceleration}"
11111            );
11112        }
11113    }
11114
11115    #[test]
11116    fn fast_continue_matches_upstream_bytes_with_dictionary() {
11117        unsafe {
11118            let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
11119            let input = b"abcdefghijklmnop0123456789ZZabcdefghijklmnop0123456789";
11120            for acceleration in [1, 4] {
11121                let expected = decode_hex("0f1a00072f5a5a1c0002503536373839");
11122                let stream = LZ4_createStream();
11123                assert!(!stream.is_null());
11124                assert_eq!(
11125                    LZ4_loadDict(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11126                    dict.len() as c_int
11127                );
11128
11129                let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11130                let compressed_len = LZ4_compress_fast_continue(
11131                    stream,
11132                    input.as_ptr() as *const c_char,
11133                    compressed.as_mut_ptr() as *mut c_char,
11134                    input.len() as c_int,
11135                    compressed.len() as c_int,
11136                    acceleration,
11137                );
11138                LZ4_freeStream(stream);
11139
11140                assert_eq!(compressed_len as usize, expected.len(), "{acceleration}");
11141                assert_eq!(
11142                    &compressed[..compressed_len as usize],
11143                    &expected,
11144                    "{acceleration}"
11145                );
11146            }
11147        }
11148    }
11149
11150    #[test]
11151    fn hc_frame_matches_upstream_bytes_for_representative_input() {
11152        unsafe {
11153            let input = patterned_hc_input(1024);
11154            let cases = [
11155                (
11156                    9,
11157                    "04224d186440a78f000000ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000e4e000f680033144662000eb6000f680033144562000f1e0140011a00144462002f6869860143144362002f61628601430445023f43333486014305a7022f6d6e86014305a7022f666786014305a7022f383986014305a7020f34001550666768696a00000000e112173a",
11158                ),
11159                (
11160                    10,
11161                    "04224d186440a796000000ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000f4e00320f1a0000144662000fb60039081a00144562000f1e0140011a00144462000f1e0140011a00144362000f1e0140011a00144262000f1e0140011a0005a7020fa4024235333435a7020fa40242356d6e6fa7020fa4024235666768a7020f34001550666768696a00000000e112173a",
11162                ),
11163                (
11164                    12,
11165                    "04224d186440a78f000000ff144142434445464741426a6b6c6d6e6f70303132333435363738396162636465666768691a002b144762000e4e000f680033144662000fb60039081a00144562000f1e0140011a00144462002f6869860143144362002f6162860143144262002f333486014305a7022f6d6e86014305a7022f666786014305a7022f383986014305a7020f34001550666768696a00000000e112173a",
11166                ),
11167            ];
11168
11169            for (level, expected_hex) in cases {
11170                let expected = decode_hex(expected_hex);
11171                let prefs = hc_frame_fixture_prefs(level);
11172                let bound = LZ4F_compressFrameBound(input.len(), &prefs);
11173                let mut encoded = vec![0u8; bound];
11174                let encoded_len = LZ4F_compressFrame(
11175                    encoded.as_mut_ptr() as *mut c_void,
11176                    encoded.len(),
11177                    input.as_ptr() as *const c_void,
11178                    input.len(),
11179                    &prefs,
11180                );
11181
11182                assert_eq!(LZ4F_isError(encoded_len), 0, "level {level}");
11183                assert_eq!(encoded_len, expected.len(), "level {level}");
11184                assert_eq!(&encoded[..encoded_len], &expected, "level {level}");
11185            }
11186        }
11187    }
11188
11189    #[test]
11190    fn frame_levels_one_and_two_use_fast_path() {
11191        unsafe {
11192            let input = patterned_hc_input(32 * 1024);
11193            let mut expected = Vec::new();
11194            for level in [0, 1, 2] {
11195                let prefs = hc_frame_fixture_prefs(level);
11196                let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
11197                let encoded_len = LZ4F_compressFrame(
11198                    encoded.as_mut_ptr() as *mut c_void,
11199                    encoded.len(),
11200                    input.as_ptr() as *const c_void,
11201                    input.len(),
11202                    &prefs,
11203                );
11204                assert_eq!(LZ4F_isError(encoded_len), 0, "level {level}");
11205                encoded.truncate(encoded_len);
11206                if level == 0 {
11207                    expected = encoded;
11208                } else {
11209                    assert_eq!(encoded, expected, "level {level}");
11210                }
11211            }
11212        }
11213    }
11214
11215    #[test]
11216    fn hc_frame_cdict_matches_upstream_bytes() {
11217        unsafe {
11218            let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
11219            let input = b"abcdefghijklmnop0123456789ZZabcdefghijklmnop0123456789";
11220            let expected = decode_hex(
11221                "04224d186440a7100000000f1a00072f5a5a1c000250353637383900000000af554433",
11222            );
11223            let prefs = hc_frame_fixture_prefs(9);
11224            let cdict = LZ4F_createCDict(dict.as_ptr() as *const c_void, dict.len());
11225            assert!(!cdict.is_null());
11226            let mut cctx = LZ4FCompressionContext(ptr::null_mut());
11227            assert_eq!(LZ4F_createCompressionContext(&mut cctx, LZ4F_VERSION), 0);
11228            let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
11229            let encoded_len = LZ4F_compressFrame_usingCDict(
11230                cctx,
11231                encoded.as_mut_ptr() as *mut c_void,
11232                encoded.len(),
11233                input.as_ptr() as *const c_void,
11234                input.len(),
11235                cdict,
11236                &prefs,
11237            );
11238            LZ4F_freeCompressionContext(cctx);
11239            LZ4F_freeCDict(cdict);
11240
11241            assert_eq!(LZ4F_isError(encoded_len), 0);
11242            assert_eq!(encoded_len, expected.len());
11243            assert_eq!(&encoded[..encoded_len], &expected);
11244        }
11245    }
11246
11247    #[test]
11248    fn hc_multiblock_frame_matches_upstream_hashes() {
11249        unsafe {
11250            let input = patterned_hc_input(150_000);
11251            let cases = [
11252                (9, 4504usize, 0x859b_76b8u32),
11253                (12, 4504usize, 0x8eb7_3b33u32),
11254            ];
11255
11256            for (level, expected_len, expected_hash) in cases {
11257                let prefs = hc_frame_fixture_prefs(level);
11258                let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
11259                let encoded_len = LZ4F_compressFrame(
11260                    encoded.as_mut_ptr() as *mut c_void,
11261                    encoded.len(),
11262                    input.as_ptr() as *const c_void,
11263                    input.len(),
11264                    &prefs,
11265                );
11266
11267                assert_eq!(LZ4F_isError(encoded_len), 0, "level {level}");
11268                assert_eq!(encoded_len, expected_len, "level {level}");
11269                assert_eq!(
11270                    xxhash32(&encoded[..encoded_len], 0),
11271                    expected_hash,
11272                    "level {level}"
11273                );
11274            }
11275        }
11276    }
11277
11278    #[test]
11279    fn hc_large_frame_matches_upstream_hash() {
11280        unsafe {
11281            let input = patterned_hc_input(8 * 1024 * 1024);
11282            let prefs = hc_frame_fixture_prefs(9);
11283            let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
11284            let encoded_len = LZ4F_compressFrame(
11285                encoded.as_mut_ptr() as *mut c_void,
11286                encoded.len(),
11287                input.as_ptr() as *const c_void,
11288                input.len(),
11289                &prefs,
11290            );
11291
11292            assert_eq!(LZ4F_isError(encoded_len), 0);
11293            assert_eq!(encoded_len, 199_444);
11294            assert_eq!(xxhash32(&encoded[..encoded_len], 0), 0x6f9f_bc8e);
11295        }
11296    }
11297
11298    #[test]
11299    fn hc_large_frame_with_cli_block_size_matches_upstream_hash() {
11300        unsafe {
11301            let input = patterned_hc_input(8 * 1024 * 1024);
11302            let mut prefs = hc_frame_fixture_prefs(9);
11303            prefs.frame_info.block_size_id = BlockSize::Max4MB;
11304            let mut encoded = vec![0u8; LZ4F_compressFrameBound(input.len(), &prefs)];
11305            let encoded_len = LZ4F_compressFrame(
11306                encoded.as_mut_ptr() as *mut c_void,
11307                encoded.len(),
11308                input.as_ptr() as *const c_void,
11309                input.len(),
11310                &prefs,
11311            );
11312
11313            assert_eq!(LZ4F_isError(encoded_len), 0);
11314            assert_eq!(encoded_len, 35_508);
11315            assert_eq!(xxhash32(&encoded[..encoded_len], 0), 0x5af1_b15f);
11316        }
11317    }
11318
11319    fn hc_frame_fixture_prefs(level: u32) -> LZ4FPreferences {
11320        LZ4FPreferences {
11321            frame_info: LZ4FFrameInfo {
11322                block_size_id: BlockSize::Max64KB,
11323                block_mode: BlockMode::Independent,
11324                content_checksum_flag: ContentChecksum::ChecksumEnabled,
11325                frame_type: FrameType::Frame,
11326                content_size: 0,
11327                dict_id: 0,
11328                block_checksum_flag: BlockChecksum::NoBlockChecksum,
11329            },
11330            compression_level: level,
11331            auto_flush: 0,
11332            favor_dec_speed: 0,
11333            reserved: [0; 3],
11334        }
11335    }
11336
11337    fn patterned_hc_input(len: usize) -> Vec<u8> {
11338        let pattern = b"abcdefghijklmnop0123456789";
11339        let mut input = vec![0u8; len];
11340        for i in 0..len {
11341            input[i] = pattern[i % pattern.len()];
11342            if (i % 97) < 9 {
11343                input[i] = b'A' + (i % 7) as u8;
11344            }
11345        }
11346        input
11347    }
11348
11349    fn decode_hex(hex: &str) -> Vec<u8> {
11350        assert_eq!(hex.len() % 2, 0);
11351        (0..hex.len())
11352            .step_by(2)
11353            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
11354            .collect()
11355    }
11356
11357    fn block_has_offset_below(block: &[u8], threshold: usize) -> bool {
11358        let mut pos = 0usize;
11359        while pos < block.len() {
11360            let token = block[pos];
11361            pos += 1;
11362            let mut lit_len = (token >> 4) as usize;
11363            if lit_len == 15 {
11364                loop {
11365                    if pos >= block.len() {
11366                        return false;
11367                    }
11368                    let value = block[pos] as usize;
11369                    pos += 1;
11370                    lit_len += value;
11371                    if value != 255 {
11372                        break;
11373                    }
11374                }
11375            }
11376            pos += lit_len;
11377            if pos >= block.len() {
11378                return false;
11379            }
11380            if pos + 2 > block.len() {
11381                return false;
11382            }
11383            let offset = u16::from_le_bytes([block[pos], block[pos + 1]]) as usize;
11384            if offset < threshold {
11385                return true;
11386            }
11387            pos += 2;
11388            if token & 0x0f == 15 {
11389                loop {
11390                    if pos >= block.len() {
11391                        return false;
11392                    }
11393                    let value = block[pos] as usize;
11394                    pos += 1;
11395                    if value != 255 {
11396                        break;
11397                    }
11398                }
11399            }
11400        }
11401        false
11402    }
11403
11404    #[test]
11405    fn hc_ext_state_dest_size_and_stream_wrappers_round_trip() {
11406        let input = b"abcdefabcdefabcdefabcdef-".repeat(4096);
11407        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
11408
11409        let state_size = LZ4_sizeofStateHC();
11410        assert!(state_size > 0);
11411        let mut state = vec![0u8; state_size as usize];
11412        let mut compressed = vec![0u8; bound];
11413        let compressed_len = unsafe {
11414            LZ4_compress_HC_extStateHC(
11415                state.as_mut_ptr() as *mut c_void,
11416                input.as_ptr() as *const c_char,
11417                compressed.as_mut_ptr() as *mut c_char,
11418                input.len() as c_int,
11419                compressed.len() as c_int,
11420                9,
11421            )
11422        };
11423        assert!(compressed_len > 0);
11424
11425        let mut output = vec![0u8; input.len()];
11426        let output_len = unsafe {
11427            LZ4_decompress_safe(
11428                compressed.as_ptr() as *const c_char,
11429                output.as_mut_ptr() as *mut c_char,
11430                compressed_len,
11431                output.len() as c_int,
11432            )
11433        };
11434        assert_eq!(output_len as usize, input.len());
11435        assert_eq!(output, input);
11436
11437        let mut source_size = input.len() as c_int;
11438        let mut tiny = vec![0u8; compressed_len as usize / 2];
11439        let partial_len = unsafe {
11440            LZ4_compress_HC_destSize(
11441                state.as_mut_ptr() as *mut c_void,
11442                input.as_ptr() as *const c_char,
11443                tiny.as_mut_ptr() as *mut c_char,
11444                &mut source_size,
11445                tiny.len() as c_int,
11446                9,
11447            )
11448        };
11449        assert!(partial_len > 0);
11450        assert!(source_size > 0);
11451        assert!(source_size < input.len() as c_int);
11452
11453        let stream = unsafe { LZ4_createStreamHC() };
11454        assert!(!stream.is_null());
11455        unsafe { LZ4_resetStreamHC_fast(stream, 9) };
11456        let mut streamed = vec![0u8; bound];
11457        let streamed_len = unsafe {
11458            LZ4_compress_HC_continue(
11459                stream,
11460                input.as_ptr() as *const c_char,
11461                streamed.as_mut_ptr() as *mut c_char,
11462                input.len() as c_int,
11463                streamed.len() as c_int,
11464            )
11465        };
11466        assert!(streamed_len > 0);
11467        unsafe { LZ4_freeStreamHC(stream) };
11468
11469        output.fill(0);
11470        let output_len = unsafe {
11471            LZ4_decompress_safe(
11472                streamed.as_ptr() as *const c_char,
11473                output.as_mut_ptr() as *mut c_char,
11474                streamed_len,
11475                output.len() as c_int,
11476            )
11477        };
11478        assert_eq!(output_len as usize, input.len());
11479        assert_eq!(output, input);
11480    }
11481
11482    #[test]
11483    fn hc_deprecated_state_wrappers_round_trip_and_reset() {
11484        let input = b"deprecated-state-hc-wrapper-".repeat(2048);
11485        let bound = unsafe { LZ4_compressBound(input.len() as c_int) } as usize;
11486        let state_size = LZ4_sizeofStreamStateHC();
11487        assert!(state_size > 0);
11488        let mut state = vec![0u8; state_size as usize];
11489
11490        let reset = unsafe {
11491            LZ4_resetStreamStateHC(
11492                state.as_mut_ptr() as *mut c_void,
11493                input.as_ptr() as *mut c_char,
11494            )
11495        };
11496        assert_eq!(reset, 0);
11497        assert_eq!(
11498            unsafe { LZ4_resetStreamStateHC(ptr::null_mut(), ptr::null_mut()) },
11499            1
11500        );
11501
11502        let mut compressed = vec![0u8; bound];
11503        let compressed_len = unsafe {
11504            LZ4_compressHC2_withStateHC(
11505                state.as_mut_ptr() as *mut c_void,
11506                input.as_ptr() as *const c_char,
11507                compressed.as_mut_ptr() as *mut c_char,
11508                input.len() as c_int,
11509                9,
11510            )
11511        };
11512        assert!(compressed_len > 0);
11513
11514        let mut output = vec![0u8; input.len()];
11515        let output_len = unsafe {
11516            LZ4_decompress_safe(
11517                compressed.as_ptr() as *const c_char,
11518                output.as_mut_ptr() as *mut c_char,
11519                compressed_len,
11520                output.len() as c_int,
11521            )
11522        };
11523        assert_eq!(output_len as usize, input.len());
11524        assert_eq!(output, input);
11525
11526        let mut limited = vec![0u8; compressed_len as usize];
11527        let limited_len = unsafe {
11528            LZ4_compressHC_limitedOutput_withStateHC(
11529                state.as_mut_ptr() as *mut c_void,
11530                input.as_ptr() as *const c_char,
11531                limited.as_mut_ptr() as *mut c_char,
11532                input.len() as c_int,
11533                limited.len() as c_int,
11534            )
11535        };
11536        assert!(limited_len > 0);
11537
11538        output.fill(0);
11539        let output_len = unsafe {
11540            LZ4_decompress_safe(
11541                limited.as_ptr() as *const c_char,
11542                output.as_mut_ptr() as *mut c_char,
11543                limited_len,
11544                output.len() as c_int,
11545            )
11546        };
11547        assert_eq!(output_len as usize, input.len());
11548        assert_eq!(output, input);
11549    }
11550
11551    #[test]
11552    fn hc_stream_compression_references_loaded_dictionary() {
11553        unsafe {
11554            let dict = b"abcdefghijklmnop";
11555            let input = b"abcdefghijklmnop";
11556            let stream = LZ4_createStreamHC();
11557            assert!(!stream.is_null());
11558            assert_eq!(
11559                LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11560                dict.len() as c_int
11561            );
11562
11563            let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11564            let compressed_len = LZ4_compress_HC_continue(
11565                stream,
11566                input.as_ptr() as *const c_char,
11567                compressed.as_mut_ptr() as *mut c_char,
11568                input.len() as c_int,
11569                compressed.len() as c_int,
11570            );
11571            assert!(compressed_len > 0);
11572            assert!((compressed_len as usize) < input.len() + 1);
11573            LZ4_freeStreamHC(stream);
11574
11575            let mut output = vec![0u8; input.len()];
11576            let output_len = LZ4_decompress_safe_usingDict(
11577                compressed.as_ptr() as *const c_char,
11578                output.as_mut_ptr() as *mut c_char,
11579                compressed_len,
11580                output.len() as c_int,
11581                dict.as_ptr() as *const c_char,
11582                dict.len() as c_int,
11583            );
11584            assert_eq!(output_len as usize, input.len());
11585            assert_eq!(output, input);
11586        }
11587    }
11588
11589    #[test]
11590    fn hc_mid_levels_continue_match_upstream_bytes_with_dictionary() {
11591        unsafe {
11592            let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
11593            let input = b"abcdefghijklmnop0123456789ZZabcdefghijklmnop0123456789";
11594            let expected = decode_hex("0f1a00072f5a5a1c0002503536373839");
11595
11596            for level in [1, 2] {
11597                let stream = LZ4_createStreamHC();
11598                assert!(!stream.is_null());
11599                LZ4_setCompressionLevel(stream, level);
11600                assert_eq!(
11601                    LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11602                    dict.len() as c_int
11603                );
11604
11605                let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11606                let compressed_len = LZ4_compress_HC_continue(
11607                    stream,
11608                    input.as_ptr() as *const c_char,
11609                    compressed.as_mut_ptr() as *mut c_char,
11610                    input.len() as c_int,
11611                    compressed.len() as c_int,
11612                );
11613                LZ4_freeStreamHC(stream);
11614
11615                assert_eq!(compressed_len as usize, expected.len(), "level {level}");
11616                assert_eq!(
11617                    &compressed[..compressed_len as usize],
11618                    &expected,
11619                    "level {level}"
11620                );
11621            }
11622        }
11623    }
11624
11625    /// Targeted byte-level parity check for HC pattern-analysis across the
11626    /// dictionary/prefix boundary. The dictionary ends in a 200-byte run of
11627    /// `'A'`, and the input also contains a 200-byte run of `'A'` partway
11628    /// through, so the HC pattern-analysis branch can extend its pattern
11629    /// reverse-count into the dict area only as far as upstream allows.
11630    #[test]
11631    fn hc_pattern_analysis_across_dict_boundary_matches_upstream_bytes() {
11632        let mut dict = vec![b'A'; 200];
11633        for i in 0..56 {
11634            dict.push(b'a' + ((i % 26) as u8));
11635        }
11636        assert_eq!(dict.len(), 256);
11637
11638        let mut input = Vec::with_capacity(512);
11639        for i in 0..64 {
11640            input.push(b'b' + ((i % 13) as u8));
11641        }
11642        input.extend(std::iter::repeat_n(b'A', 200));
11643        for i in 0..(512 - 264) {
11644            input.push(b'c' + ((i % 13) as u8));
11645        }
11646        assert_eq!(input.len(), 512);
11647
11648        let cases: &[(c_int, &str)] = &[
11649            (3, "091d000f0d00201f410100b408e0001f6f0d00d3506c6d6e6f63"),
11650            (4, "091d000f0d00201f410100b40924010f0d00d3506c6d6e6f63"),
11651            (5, "091d000f0d00201f410100b40924010f0d00d3506c6d6e6f63"),
11652            (6, "091d000f0d00201f410100b40924010f0d00d3506c6d6e6f63"),
11653            (7, "091d000f0d00201f410100b40924010f0d00d3506c6d6e6f63"),
11654            (8, "091d000f0d00201f410100b40924010f0d00d3506c6d6e6f63"),
11655            (9, "091d000f0d00200f4001b50924010f0d00d3506c6d6e6f63"),
11656            (10, "091d000f0d00200f4001b50924010f0d00d3506c6d6e6f63"),
11657            (11, "091d000f0d00200f4001b50924010f0d00d3506c6d6e6f63"),
11658            (12, "091d000f0d00200f4001b50924010f0d00d3506c6d6e6f63"),
11659        ];
11660
11661        for (level, expected_hex) in cases {
11662            let expected = decode_hex(expected_hex);
11663            unsafe {
11664                let stream = LZ4_createStreamHC();
11665                assert!(!stream.is_null());
11666                LZ4_resetStreamHC_fast(stream, *level);
11667                assert_eq!(
11668                    LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11669                    dict.len() as c_int
11670                );
11671
11672                let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11673                let compressed_len = LZ4_compress_HC_continue(
11674                    stream,
11675                    input.as_ptr() as *const c_char,
11676                    compressed.as_mut_ptr() as *mut c_char,
11677                    input.len() as c_int,
11678                    compressed.len() as c_int,
11679                );
11680                LZ4_freeStreamHC(stream);
11681
11682                assert_eq!(compressed_len as usize, expected.len(), "level {level}");
11683                assert_eq!(
11684                    &compressed[..compressed_len as usize],
11685                    &expected,
11686                    "level {level}"
11687                );
11688            }
11689        }
11690    }
11691
11692    /// Byte-level parity check for `LZ4_compress_HC_continue()` on the
11693    /// optimal HC levels (10..=12) with a 256-byte loaded dictionary and a
11694    /// 1024-byte input. Mirrors the hash-chain version below but covers
11695    /// `compress_block_hc_optimal()` with a non-zero `base`.
11696    #[test]
11697    fn hc_optimal_levels_continue_match_upstream_bytes_with_dictionary() {
11698        let dict = patterned_hc_input(256);
11699        let mut input = patterned_hc_input(1024);
11700        for (i, byte) in input.iter_mut().enumerate() {
11701            *byte ^= ((i >> 7) & 1) as u8;
11702        }
11703        let cases: &[(c_int, &str)] = &[
11704            (10, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd00014234043c4000f1e013d049c0005a7020fa40342356d6e6fa7030a1a000fd00037144409030f34001550676669686b"),
11705            (11, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000f0401260f1a000c144262000e34000fd000330445021f434e001e0fd00014234043c4000f1e013d049c0005a7020ea4030fee013305a7030a1a000fd00037144409030f34001550676669686b"),
11706            (12, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159f474640434245444746340018061a000fd00010144562010f1e014010339c00234544c4000f0401260f1a000c144262000e34000fd000330445021f434e001e0fd00014234043c4000f1e013d049c0005a7020fa40341001a0005a7030a1a000fd00037144409030f34001550676669686b"),
11707        ];
11708
11709        for (level, expected_hex) in cases {
11710            let expected = decode_hex(expected_hex);
11711            unsafe {
11712                let stream = LZ4_createStreamHC();
11713                assert!(!stream.is_null());
11714                LZ4_resetStreamHC_fast(stream, *level);
11715                assert_eq!(
11716                    LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11717                    dict.len() as c_int
11718                );
11719
11720                let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11721                let compressed_len = LZ4_compress_HC_continue(
11722                    stream,
11723                    input.as_ptr() as *const c_char,
11724                    compressed.as_mut_ptr() as *mut c_char,
11725                    input.len() as c_int,
11726                    compressed.len() as c_int,
11727                );
11728                LZ4_freeStreamHC(stream);
11729
11730                assert_eq!(compressed_len as usize, expected.len(), "level {level}");
11731                assert_eq!(
11732                    &compressed[..compressed_len as usize],
11733                    &expected,
11734                    "level {level}"
11735                );
11736            }
11737        }
11738    }
11739
11740    /// Byte-level parity check for `LZ4_compress_HC_continue()` across the
11741    /// hash-chain HC levels (3..=9) with a 256-byte loaded dictionary and a
11742    /// 1024-byte input. Fixtures were produced by the upstream C library
11743    /// using the same `patterned_hc_input()` helper the Rust tests use.
11744    #[test]
11745    fn hc_hashchain_levels_continue_match_upstream_bytes_with_dictionary() {
11746        let dict = patterned_hc_input(256);
11747        let mut input = patterned_hc_input(1024);
11748        for (i, byte) in input.iter_mut().enumerate() {
11749            *byte ^= ((i >> 7) & 1) as u8;
11750        }
11751        let cases: &[(c_int, &str)] = &[
11752            (3, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010fea00070f1a0027009c00234544c4000fb600070f1a002b144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020fb600070f1a002b05a7030a1a000fd000370445021f4734001550676669686b"),
11753            (4, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020ed4010fee013305a7030a1a000fd000370445021f4734001550676669686b"),
11754            (5, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020ed4010f0c033305a7030a1a000fd000370445021f4734001550676669686b"),
11755            (6, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020fa40342356d6e6fa7030a1a000fd000370445021f4734001550676669686b"),
11756            (7, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020fa40342356d6e6fa7030a1a000fd000370445021f4734001550676669686b"),
11757            (8, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020fa40342356d6e6fa7030a1a000fd000370445021f4734001550676669686b"),
11758            (9, "0f00016dff0b39386063626564676669686b6a6d6c6f6e7131303332353437361a00159e47464043424544474634000f4e00100fd00010144562010f1e014010339c00234544c4000e04010f1e012e011a00144262000e34000fd000330445021f434e001e0fd0001403e3012f40431e013d049c0005a7020fa40342356d6e6fa7030a1a000fd000370445021f4734001550676669686b"),
11759        ];
11760
11761        for (level, expected_hex) in cases {
11762            let expected = decode_hex(expected_hex);
11763            unsafe {
11764                let stream = LZ4_createStreamHC();
11765                assert!(!stream.is_null());
11766                LZ4_resetStreamHC_fast(stream, *level);
11767                assert_eq!(
11768                    LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict.len() as c_int),
11769                    dict.len() as c_int
11770                );
11771
11772                let mut compressed = vec![0u8; LZ4_compressBound(input.len() as c_int) as usize];
11773                let compressed_len = LZ4_compress_HC_continue(
11774                    stream,
11775                    input.as_ptr() as *const c_char,
11776                    compressed.as_mut_ptr() as *mut c_char,
11777                    input.len() as c_int,
11778                    compressed.len() as c_int,
11779                );
11780                LZ4_freeStreamHC(stream);
11781
11782                assert_eq!(compressed_len as usize, expected.len(), "level {level}");
11783                assert_eq!(
11784                    &compressed[..compressed_len as usize],
11785                    &expected,
11786                    "level {level}"
11787                );
11788            }
11789        }
11790    }
11791}