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}