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