Skip to main content

lz4_sys/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Raw FFI bindings to the LZ4 library built by `//third_party/lz4`, which this crate also links.
6//!
7//! These are hand-written declarations of the subset of `lz4.h` and `lz4hc.h` that is used from
8//! Rust, and must be kept in sync with those headers and the defines that `//third_party/lz4` is
9//! built with. This crate is `no_std` so that it can also be used by the kernel.
10
11#![no_std]
12
13#[cfg(test)]
14extern crate std;
15
16use core::ffi::{c_char, c_int, c_void};
17
18/// `LZ4_MEMORY_USAGE`: log2 of the size of the hash table used for compression.
19/// `//third_party/lz4` does not override it, so this is `LZ4_MEMORY_USAGE_DEFAULT`.
20pub const LZ4_MEMORY_USAGE: u32 = 14;
21
22/// `LZ4_MAX_INPUT_SIZE`: the maximum number of bytes that LZ4 can compress in one call.
23pub const LZ4_MAX_INPUT_SIZE: c_int = 0x7E000000;
24
25/// `LZ4_STREAM_MINSIZE`: the size of [`LZ4_stream_t`].
26pub const LZ4_STREAM_MINSIZE: usize = (1 << LZ4_MEMORY_USAGE) + 32;
27
28/// `LZ4HC_CLEVEL_MIN`: the minimum compression level of [`LZ4_compress_HC`].
29pub const LZ4HC_CLEVEL_MIN: c_int = 2;
30/// `LZ4HC_CLEVEL_DEFAULT`: the default compression level of [`LZ4_compress_HC`].
31pub const LZ4HC_CLEVEL_DEFAULT: c_int = 9;
32/// `LZ4HC_CLEVEL_OPT_MIN`: the minimum compression level of [`LZ4_compress_HC`] that uses the
33/// optimal parser.
34pub const LZ4HC_CLEVEL_OPT_MIN: c_int = 10;
35/// `LZ4HC_CLEVEL_MAX`: the maximum compression level of [`LZ4_compress_HC`].
36pub const LZ4HC_CLEVEL_MAX: c_int = 12;
37
38/// `LZ4_stream_t`: the state used by the LZ4 streaming and external state compression functions.
39///
40/// The contents are opaque, and this only matches the size and alignment of the C
41/// `union LZ4_stream_u`, which is sized by `LZ4_STREAM_MINSIZE` and contains pointers and
42/// 32-bit integers. It must be initialized with [`LZ4_initStream`] before being used, which
43/// fails if the size or alignment does not satisfy the linked LZ4 library.
44///
45/// This is intentionally neither `Copy` nor `Clone` as it is large and must not be implicitly
46/// copied.
47#[repr(C)]
48pub struct LZ4_stream_t {
49    // `usize` has the same alignment as a pointer on all supported targets, which is the largest
50    // alignment of any member of `LZ4_stream_t_internal`.
51    _align: [usize; 0],
52    _opaque: [u8; LZ4_STREAM_MINSIZE],
53}
54
55unsafe extern "C" {
56    /// `LZ4_compress_default()`: compresses `srcSize` bytes from `src` into the `dstCapacity`
57    /// byte buffer `dst`. Returns the number of bytes written to `dst`, or 0 if compression fails.
58    /// Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)`.
59    pub fn LZ4_compress_default(
60        src: *const c_char,
61        dst: *mut c_char,
62        srcSize: c_int,
63        dstCapacity: c_int,
64    ) -> c_int;
65
66    /// `LZ4_decompress_safe()`: decompresses the complete compressed block of `compressedSize`
67    /// bytes from `src` into the `dstCapacity` byte buffer `dst`. Returns the number of bytes
68    /// written to `dst`, or a negative value if `dst` is too small or `src` is malformed.
69    ///
70    /// This never reads outside of `src` or writes outside of `dst`, even if `src` is malformed.
71    pub fn LZ4_decompress_safe(
72        src: *const c_char,
73        dst: *mut c_char,
74        compressedSize: c_int,
75        dstCapacity: c_int,
76    ) -> c_int;
77
78    /// `LZ4_compressBound()`: returns the maximum size of the output of compressing `inputSize`
79    /// bytes, or 0 if `inputSize` is larger than [`LZ4_MAX_INPUT_SIZE`].
80    pub fn LZ4_compressBound(inputSize: c_int) -> c_int;
81
82    /// `LZ4_initStream()`: initializes the `size` byte buffer `buffer` as an [`LZ4_stream_t`].
83    /// Returns `buffer` on success, or null if `size` is too small or `buffer` is not aligned
84    /// correctly.
85    pub fn LZ4_initStream(buffer: *mut c_void, size: usize) -> *mut LZ4_stream_t;
86
87    /// `LZ4_compress_fast_extState_fastReset()`: compresses `srcSize` bytes from `src` into the
88    /// `dstCapacity` byte buffer `dst`, using the `acceleration` factor and the [`LZ4_stream_t`]
89    /// `state`. Returns the number of bytes written to `dst`, or 0 if compression fails.
90    ///
91    /// This avoids the expensive initialization of `state` done by
92    /// `LZ4_compress_fast_extState()`, and so is only safe to call if `state` is already correctly
93    /// initialized, such as by [`LZ4_initStream`] or a previous call to this function. `state` is
94    /// left correctly initialized afterwards, even if compression fails.
95    ///
96    /// This function is only declared by `lz4.h` with `LZ4_STATIC_LINKING_ONLY`, as it is not
97    /// part of the stable ABI of shared builds of LZ4, but is always available as this crate
98    /// statically links LZ4.
99    pub fn LZ4_compress_fast_extState_fastReset(
100        state: *mut c_void,
101        src: *const c_char,
102        dst: *mut c_char,
103        srcSize: c_int,
104        dstCapacity: c_int,
105        acceleration: c_int,
106    ) -> c_int;
107
108    /// `LZ4_compress_HC()`: compresses `srcSize` bytes from `src` into the `dstCapacity` byte
109    /// buffer `dst` using the high compression (HC) algorithm at `compressionLevel`. Returns the
110    /// number of bytes written to `dst`, or 0 if compression fails. Compression is guaranteed to
111    /// succeed if `dstCapacity >= LZ4_compressBound(srcSize)`.
112    pub fn LZ4_compress_HC(
113        src: *const c_char,
114        dst: *mut c_char,
115        srcSize: c_int,
116        dstCapacity: c_int,
117        compressionLevel: c_int,
118    ) -> c_int;
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use core::mem::MaybeUninit;
125    use std::boxed::Box;
126    use std::vec;
127
128    const ACCELERATION: c_int = 1;
129
130    #[test]
131    fn test_stream_size() {
132        let mut stream = Box::<LZ4_stream_t>::new_uninit();
133        let ptr = stream.as_mut_ptr();
134        // SAFETY: `ptr` is valid for writes of `size_of::<LZ4_stream_t>() - 1` bytes.
135        let initialized = unsafe {
136            LZ4_initStream(ptr.cast::<c_void>(), core::mem::size_of::<LZ4_stream_t>() - 1)
137        };
138        assert!(initialized.is_null());
139        // SAFETY: `ptr` is valid for writes of `size_of::<LZ4_stream_t>()` bytes.
140        let initialized =
141            unsafe { LZ4_initStream(ptr.cast::<c_void>(), core::mem::size_of::<LZ4_stream_t>()) };
142        assert_eq!(initialized, ptr);
143    }
144
145    #[test]
146    fn test_compress_bound() {
147        // SAFETY: No unsafe parameters.
148        unsafe {
149            assert!(LZ4_compressBound(LZ4_MAX_INPUT_SIZE) > 0);
150            assert_eq!(LZ4_compressBound(LZ4_MAX_INPUT_SIZE + 1), 0);
151        }
152    }
153
154    #[test]
155    fn test_ext_state_fast_reset_roundtrip() {
156        let mut stream: Box<MaybeUninit<LZ4_stream_t>> = Box::new_uninit();
157        let stream = stream.as_mut_ptr();
158        // SAFETY: `stream` is valid for writes of `size_of::<LZ4_stream_t>()` bytes.
159        let initialized = unsafe {
160            LZ4_initStream(stream.cast::<c_void>(), core::mem::size_of::<LZ4_stream_t>())
161        };
162        assert_eq!(initialized, stream);
163
164        // Compress several inputs to check that the stream is reusable.
165        for data in [b"Hello, world! LZ4 compression test.".as_slice(), &[0u8; 4096], &[7u8; 100]] {
166            let src_size = c_int::try_from(data.len()).unwrap();
167            // SAFETY: No unsafe parameters.
168            let bound = unsafe { LZ4_compressBound(src_size) };
169            assert!(bound > 0);
170            let mut compressed = vec![0u8; bound as usize];
171            // SAFETY: `stream` was initialized above, and is left initialized by every call.
172            // `data` is valid for reads of `src_size` bytes and `compressed` is valid for writes
173            // of `bound` bytes.
174            let compressed_size = unsafe {
175                LZ4_compress_fast_extState_fastReset(
176                    stream.cast::<c_void>(),
177                    data.as_ptr().cast::<c_char>(),
178                    compressed.as_mut_ptr().cast::<c_char>(),
179                    src_size,
180                    bound,
181                    ACCELERATION,
182                )
183            };
184            assert!(compressed_size > 0 && compressed_size <= bound);
185
186            let mut decompressed = vec![0u8; data.len()];
187            // SAFETY: `compressed` is valid for reads of `compressed_size` bytes and
188            // `decompressed` is valid for writes of `src_size` bytes.
189            let decompressed_size = unsafe {
190                LZ4_decompress_safe(
191                    compressed.as_ptr().cast::<c_char>(),
192                    decompressed.as_mut_ptr().cast::<c_char>(),
193                    compressed_size,
194                    src_size,
195                )
196            };
197            assert_eq!(decompressed_size, src_size);
198            assert_eq!(decompressed.as_slice(), data);
199        }
200    }
201
202    #[test]
203    fn test_ext_state_fast_reset_dst_too_small() {
204        let mut stream: Box<MaybeUninit<LZ4_stream_t>> = Box::new_uninit();
205        let stream = stream.as_mut_ptr();
206        // SAFETY: `stream` is valid for writes of `size_of::<LZ4_stream_t>()` bytes.
207        let initialized = unsafe {
208            LZ4_initStream(stream.cast::<c_void>(), core::mem::size_of::<LZ4_stream_t>())
209        };
210        assert_eq!(initialized, stream);
211
212        let data = b"Hello, world! LZ4 compression test.";
213        let mut compressed = [0u8; 4];
214        // SAFETY: `stream` was initialized above, `data` is valid for reads of `data.len()` bytes
215        // and `compressed` is valid for writes of `compressed.len()` bytes.
216        let compressed_size = unsafe {
217            LZ4_compress_fast_extState_fastReset(
218                stream.cast::<c_void>(),
219                data.as_ptr().cast::<c_char>(),
220                compressed.as_mut_ptr().cast::<c_char>(),
221                data.len() as c_int,
222                compressed.len() as c_int,
223                ACCELERATION,
224            )
225        };
226        assert_eq!(compressed_size, 0);
227    }
228}