1use std::num::NonZero;
6use std::os::raw::{c_char, c_int};
7
8unsafe extern "C" {
9 fn LZ4_compress_default(
10 src: *const c_char,
11 dst: *mut c_char,
12 srcSize: c_int,
13 dstCapacity: c_int,
14 ) -> c_int;
15 fn LZ4_decompress_safe(
16 src: *const c_char,
17 dst: *mut c_char,
18 compressedSize: c_int,
19 dstCapacity: c_int,
20 ) -> c_int;
21 fn LZ4_compressBound(inputSize: c_int) -> c_int;
22 fn LZ4_compress_HC(
23 src: *const c_char,
24 dst: *mut c_char,
25 srcSize: c_int,
26 dstCapacity: c_int,
27 compressionLevel: c_int,
28 ) -> c_int;
29}
30
31#[derive(thiserror::Error, Debug)]
32pub enum Error {
33 #[error("LZ4 only supports compressing up to 2016MiB")]
34 InputTooLarge,
35 #[error("LZ4 decompression failed")]
36 DecompressionFailed,
37}
38
39fn compress_bound(input_size: c_int) -> Result<NonZero<c_int>, Error> {
40 let bound = unsafe { LZ4_compressBound(input_size) };
42 NonZero::<c_int>::new(bound).ok_or(Error::InputTooLarge)
43}
44
45pub fn compress(data: &[u8]) -> Vec<u8> {
47 if data.is_empty() {
48 return Vec::new();
49 }
50 let src_size = data.len() as c_int;
51 let bound = unsafe { LZ4_compressBound(src_size) };
52 let mut compressed = vec![0u8; bound as usize];
53 let compressed_size = unsafe {
54 LZ4_compress_default(
55 data.as_ptr() as *const c_char,
56 compressed.as_mut_ptr() as *mut c_char,
57 src_size,
58 bound,
59 )
60 };
61 assert!(compressed_size > 0, "LZ4 compression failed");
62 compressed.truncate(compressed_size as usize);
63 compressed
64}
65
66pub fn decompress(data: &[u8], uncompressed_size: usize) -> Result<Vec<u8>, i32> {
68 let mut decompressed = vec![0u8; uncompressed_size];
69 let bytes_written = decompress_into(data, &mut decompressed).map_err(|_| -1)?;
70 if bytes_written != uncompressed_size {
71 return Err(-1);
72 }
73 Ok(decompressed)
74}
75
76pub fn decompress_into(data: &[u8], destination: &mut [u8]) -> Result<usize, Error> {
79 if data.is_empty() {
80 return Ok(0);
81 }
82 if destination.is_empty() {
83 return Err(Error::DecompressionFailed);
90 }
91 let result = unsafe {
92 LZ4_decompress_safe(
93 data.as_ptr() as *const c_char,
94 destination.as_mut_ptr() as *mut c_char,
95 data.len().try_into().map_err(|_| Error::InputTooLarge)?,
96 destination.len().try_into().map_err(|_| Error::InputTooLarge)?,
97 )
98 };
99 if result < 0 { Err(Error::DecompressionFailed) } else { Ok(result as usize) }
100}
101
102#[derive(Copy, Clone)]
104pub struct HcCompressionLevel(i32);
105
106impl HcCompressionLevel {
107 pub const MIN: Self = Self(3);
108 pub const DEFAULT: Self = Self(9);
109 pub const OPT_MIN: Self = Self(10);
110 pub const MAX: Self = Self(12);
111
112 pub fn custom(level: i32) -> Self {
113 Self(level)
114 }
115}
116
117impl From<HcCompressionLevel> for i32 {
118 fn from(level: HcCompressionLevel) -> Self {
119 level.0
120 }
121}
122
123pub fn compress_hc(data: &[u8], compression_level: HcCompressionLevel) -> Result<Vec<u8>, Error> {
125 if data.is_empty() {
126 return Ok(Vec::new());
127 }
128 let data_size = i32::try_from(data.len()).map_err(|_| Error::InputTooLarge)?;
129 let bound = compress_bound(data_size)?.get();
130 let mut compressed = Vec::with_capacity(bound as usize);
131 unsafe {
137 let dst = compressed.spare_capacity_mut()[0..bound as usize].as_mut_ptr().cast::<c_char>();
138 let src = data.as_ptr().cast::<c_char>();
139 let compressed_size = LZ4_compress_HC(src, dst, data_size, bound, compression_level.into());
140 assert!(compressed_size > 0, "LZ4 compression failed");
143 compressed.set_len(compressed_size as usize);
144 }
145 Ok(compressed)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_roundtrip() {
154 let data = b"Hello, world! LZ4 compression test.";
155 let compressed = compress(data);
156 let decompressed = decompress(&compressed, data.len()).unwrap();
157 assert_eq!(data.as_slice(), decompressed.as_slice());
158 }
159
160 #[test]
161 fn test_empty() {
162 let data = b"";
163 let compressed = compress(data);
164 let decompressed = decompress(&compressed, 0).unwrap();
165 assert_eq!(data.as_slice(), decompressed.as_slice());
166 }
167
168 #[test]
169 fn test_compress_bound() {
170 assert!(compress_bound(0).is_ok());
171 assert!(compress_bound(20).is_ok());
172 assert!(compress_bound(i32::MAX).is_err());
173 assert!(compress_bound(0x7E000000).is_ok());
174 assert!(compress_bound(0x7E000000 + 1).is_err());
175 }
176
177 #[test]
178 fn test_hc_roundtrip() {
179 let data = b"Hello, world! LZ4 compression test.";
180 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
181 let decompressed = decompress(&compressed, data.len()).unwrap();
182 assert_eq!(data.as_slice(), decompressed.as_slice());
183 }
184
185 #[test]
186 fn test_hc_empty_roundtrip() {
187 let data = b"";
188 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
189 let decompressed = decompress(&compressed, data.len()).unwrap();
190 assert_eq!(data.as_slice(), decompressed.as_slice());
191 }
192 #[test]
193 fn test_decompress_into_zero_buffer() {
194 let data = b"Hello, world! LZ4 compression test.";
195 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
196 decompress_into(&compressed, &mut Vec::new()).expect_err("buf should be too small");
197 }
198
199 #[test]
200 fn test_decompress_into_buffer_too_small() {
201 let data = b"Hello, world! LZ4 compression test.";
202 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
203 let mut buf = vec![0; data.len() - 1];
204 decompress_into(&compressed, &mut buf).expect_err("buf should be too small");
205 }
206
207 #[test]
208 fn test_decompress_into_large_buffer() {
209 let data = b"Hello, world! LZ4 compression test.";
210 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
211 let mut buf = vec![0; data.len() + 1];
212 let bytes_written = decompress_into(&compressed, &mut buf).unwrap();
213 assert_eq!(data.as_slice(), &buf[0..bytes_written]);
214 }
215
216 #[test]
217 fn test_decompress_into_exact_sized_buffer() {
218 let data = b"Hello, world! LZ4 compression test.";
219 let compressed = compress_hc(data, HcCompressionLevel::MAX).unwrap();
220 let mut buf = vec![0; data.len()];
221 let bytes_written = decompress_into(&compressed, &mut buf).unwrap();
222 assert_eq!(data.len(), bytes_written);
223 assert_eq!(data.as_slice(), buf.as_slice());
224 }
225
226 #[test]
227 fn test_empty_decompress_into_large_buffer() {
228 let mut buf = vec![0; 100];
229 let decompressed_bytes =
230 decompress_into(&[], &mut buf).expect("decompression should succeed");
231 assert_eq!(decompressed_bytes, 0);
232 }
233}