delivery_blob/compression/
compression_algorithm.rs1use super::{FormatError, ZstdError};
12use crate::compression::ChunkedArchiveError;
13use std::mem::MaybeUninit;
14use std::ptr::NonNull;
15use storage_ptr_slice::PtrByteSlice;
16use zstd::zstd_safe::zstd_sys;
17
18thread_local! {
19 static ZSTD_COMPRESSOR: std::cell::RefCell<zstd::zstd_safe::CCtx<'static>> =
20 std::cell::RefCell::new({
21 let mut cctx = zstd::zstd_safe::CCtx::create();
22 cctx.set_parameter(zstd::zstd_safe::CParameter::ChecksumFlag(true)).unwrap();
23 cctx
24 });
25 static ZSTD_DECOMPRESSOR: std::cell::RefCell<RawDCtx> = {
26 let raw_ptr = unsafe { zstd_sys::ZSTD_createDCtx() };
29 let ptr = NonNull::new(raw_ptr).expect("ZSTD_createDCtx failed");
30 std::cell::RefCell::new(RawDCtx(ptr))
31 };
32}
33
34struct RawDCtx(NonNull<zstd_sys::ZSTD_DCtx>);
35impl Drop for RawDCtx {
36 fn drop(&mut self) {
37 unsafe {
39 zstd_sys::ZSTD_freeDCtx(self.0.as_ptr());
40 }
41 }
42}
43
44unsafe fn zstd_decompress_ptr(
51 src: PtrByteSlice<'_>,
52 dst: *mut [MaybeUninit<u8>],
53) -> Result<usize, usize> {
54 ZSTD_DECOMPRESSOR.with_borrow_mut(|decompressor| {
55 let dctx = decompressor.0.as_ptr();
56 let result = unsafe {
60 zstd_sys::ZSTD_decompressDCtx(
61 dctx,
62 dst as *mut std::os::raw::c_void,
63 dst.len(),
64 src.as_raw_slice_ptr() as *const std::os::raw::c_void,
65 src.len(),
66 )
67 };
68 if unsafe { zstd_sys::ZSTD_isError(result) } != 0 { Err(result) } else { Ok(result) }
70 })
71}
72
73unsafe extern "C" {
74 fn LZ4_decompress_safe(
75 src: *const std::os::raw::c_char,
76 dst: *mut std::os::raw::c_char,
77 compressedSize: std::os::raw::c_int,
78 dstCapacity: std::os::raw::c_int,
79 ) -> std::os::raw::c_int;
80}
81
82unsafe fn lz4_decompress_ptr(
89 src: PtrByteSlice<'_>,
90 dst: *mut [MaybeUninit<u8>],
91) -> Result<usize, lz4::Error> {
92 if src.is_empty() {
93 return Ok(0);
94 }
95 if dst.len() == 0 {
96 return Err(lz4::Error::DecompressionFailed);
97 }
98 let result = unsafe {
102 LZ4_decompress_safe(
103 src.as_raw_slice_ptr() as *const std::os::raw::c_char,
104 dst as *mut std::os::raw::c_char,
105 src.len().try_into().map_err(|_| lz4::Error::InputTooLarge)?,
106 dst.len().try_into().map_err(|_| lz4::Error::InputTooLarge)?,
107 )
108 };
109 if result < 0 { Err(lz4::Error::DecompressionFailed) } else { Ok(result as usize) }
110}
111
112#[derive(Copy, Clone, Debug, Eq, PartialEq)]
114#[repr(u8)]
115pub enum CompressionAlgorithm {
116 Zstd = 0,
117 Lz4 = 1,
118}
119
120impl CompressionAlgorithm {
121 pub fn decompressor(&self) -> Decompressor {
124 match self {
125 Self::Zstd => Decompressor::Zstd,
126 Self::Lz4 => Decompressor::Lz4,
127 }
128 }
129
130 pub fn thread_local_decompressor(&self) -> ThreadLocalDecompressor {
135 match self {
136 Self::Zstd => ThreadLocalDecompressor::Zstd,
137 Self::Lz4 => ThreadLocalDecompressor::Lz4,
138 }
139 }
140}
141
142impl From<CompressionAlgorithm> for u8 {
143 fn from(value: CompressionAlgorithm) -> Self {
144 value as u8
145 }
146}
147
148impl TryFrom<u8> for CompressionAlgorithm {
149 type Error = ChunkedArchiveError;
150 fn try_from(value: u8) -> Result<Self, Self::Error> {
151 match value {
152 0 => Ok(CompressionAlgorithm::Zstd),
153 1 => Ok(CompressionAlgorithm::Lz4),
154 _ => Err(ChunkedArchiveError::IntegrityError),
155 }
156 }
157}
158
159pub enum Decompressor {
161 Zstd,
162 Lz4,
163}
164
165impl Decompressor {
166 pub fn decompress<'a>(
168 &mut self,
169 data: impl Into<PtrByteSlice<'a>>,
170 uncompressed_size: usize,
171 chunk_index: usize,
172 ) -> Result<Vec<u8>, ChunkedArchiveError> {
173 let src = data.into();
174 let mut buffer = Vec::with_capacity(uncompressed_size);
175 let dst = buffer.spare_capacity_mut() as *mut [MaybeUninit<u8>];
176 let len = match self {
177 Self::Zstd => {
178 unsafe { zstd_decompress_ptr(src, dst) }.map_err(|code| {
181 ChunkedArchiveError::DecompressionError {
182 index: chunk_index,
183 error: FormatError::Zstd(ZstdError(code)),
184 }
185 })?
186 }
187 Self::Lz4 => {
188 unsafe { lz4_decompress_ptr(src, dst) }.map_err(|e| {
191 ChunkedArchiveError::DecompressionError {
192 index: chunk_index,
193 error: FormatError::Lz4(e),
194 }
195 })?
196 }
197 };
198 unsafe {
200 buffer.set_len(len);
201 }
202 Ok(buffer)
203 }
204
205 pub fn decompress_into<'a>(
207 &mut self,
208 data: impl Into<PtrByteSlice<'a>>,
209 destination: &mut [u8],
210 chunk_index: usize,
211 ) -> Result<usize, ChunkedArchiveError> {
212 let src = data.into();
213 let dst = destination as *mut [u8] as *mut [MaybeUninit<u8>];
214 match self {
215 Self::Zstd => {
216 unsafe { zstd_decompress_ptr(src, dst) }.map_err(|code| {
219 ChunkedArchiveError::DecompressionError {
220 index: chunk_index,
221 error: FormatError::Zstd(ZstdError(code)),
222 }
223 })
224 }
225 Self::Lz4 => {
226 unsafe { lz4_decompress_ptr(src, dst) }.map_err(|e| {
229 ChunkedArchiveError::DecompressionError {
230 index: chunk_index,
231 error: FormatError::Lz4(e),
232 }
233 })
234 }
235 }
236 }
237}
238
239#[derive(Copy, Clone)]
240pub enum ThreadLocalDecompressor {
242 Zstd,
243 Lz4,
244}
245
246impl ThreadLocalDecompressor {
247 pub fn decompress<'a>(
249 &self,
250 data: impl Into<PtrByteSlice<'a>>,
251 uncompressed_size: usize,
252 chunk_index: usize,
253 ) -> Result<Vec<u8>, ChunkedArchiveError> {
254 let src = data.into();
255 let mut buffer = Vec::with_capacity(uncompressed_size);
256 let dst = buffer.spare_capacity_mut() as *mut [MaybeUninit<u8>];
257 let len = match self {
258 Self::Zstd => {
259 unsafe { zstd_decompress_ptr(src, dst) }.map_err(|code| {
262 ChunkedArchiveError::DecompressionError {
263 index: chunk_index,
264 error: FormatError::Zstd(ZstdError(code)),
265 }
266 })?
267 }
268 Self::Lz4 => {
269 unsafe { lz4_decompress_ptr(src, dst) }.map_err(|e| {
272 ChunkedArchiveError::DecompressionError {
273 index: chunk_index,
274 error: FormatError::Lz4(e),
275 }
276 })?
277 }
278 };
279 unsafe {
281 buffer.set_len(len);
282 }
283 Ok(buffer)
284 }
285
286 pub fn decompress_into<'a>(
288 &self,
289 data: impl Into<PtrByteSlice<'a>>,
290 destination: &mut [u8],
291 chunk_index: usize,
292 ) -> Result<usize, ChunkedArchiveError> {
293 let src = data.into();
294 let dst = destination as *mut [u8] as *mut [MaybeUninit<u8>];
295 match self {
296 Self::Zstd => {
297 unsafe { zstd_decompress_ptr(src, dst) }.map_err(|code| {
300 ChunkedArchiveError::DecompressionError {
301 index: chunk_index,
302 error: FormatError::Zstd(ZstdError(code)),
303 }
304 })
305 }
306 Self::Lz4 => {
307 unsafe { lz4_decompress_ptr(src, dst) }.map_err(|e| {
310 ChunkedArchiveError::DecompressionError {
311 index: chunk_index,
312 error: FormatError::Lz4(e),
313 }
314 })
315 }
316 }
317 }
318}
319
320pub enum Compressor {
322 Zstd(zstd::zstd_safe::CCtx<'static>),
323 Lz4 { compression_level: lz4::HcCompressionLevel },
324}
325
326impl Compressor {
327 pub fn compress(
329 &mut self,
330 data: &[u8],
331 chunk_index: usize,
332 ) -> Result<Vec<u8>, ChunkedArchiveError> {
333 match self {
334 Self::Zstd(cctx) => {
335 let buffer_len = zstd::zstd_safe::compress_bound(data.len());
336 let mut buffer = Vec::with_capacity(buffer_len);
337 match cctx.compress2(&mut buffer, data) {
338 Ok(_) => Ok(buffer),
339 Err(code) => Err(ChunkedArchiveError::CompressionError {
340 index: chunk_index,
341 error: FormatError::Zstd(ZstdError(code)),
342 }),
343 }
344 }
345 Self::Lz4 { compression_level } => {
346 lz4::compress_hc(data, *compression_level).map_err(|error| {
347 ChunkedArchiveError::CompressionError {
348 index: chunk_index,
349 error: FormatError::Lz4(error),
350 }
351 })
352 }
353 }
354 }
355}
356
357#[derive(Copy, Clone)]
358pub enum ThreadLocalCompressor {
360 Zstd { compression_level: i32 },
361 Lz4 { compression_level: lz4::HcCompressionLevel },
362}
363
364impl ThreadLocalCompressor {
365 pub fn compress(
367 &self,
368 data: &[u8],
369 chunk_index: usize,
370 ) -> Result<Vec<u8>, ChunkedArchiveError> {
371 match self {
372 Self::Zstd { compression_level } => ZSTD_COMPRESSOR.with_borrow_mut(|cctx| {
373 cctx.set_parameter(zstd::zstd_safe::CParameter::CompressionLevel(
374 *compression_level,
375 ))
376 .expect("setting the compression level should never fail");
377 let buffer_len = zstd::zstd_safe::compress_bound(data.len());
378 let mut buffer = Vec::with_capacity(buffer_len);
379 match cctx.compress2(&mut buffer, data) {
380 Ok(_) => Ok(buffer),
381 Err(code) => Err(ChunkedArchiveError::CompressionError {
382 index: chunk_index,
383 error: FormatError::Zstd(ZstdError(code)),
384 }),
385 }
386 }),
387 Self::Lz4 { compression_level } => {
388 lz4::compress_hc(data, *compression_level).map_err(|error| {
389 ChunkedArchiveError::CompressionError {
390 index: chunk_index,
391 error: FormatError::Lz4(error),
392 }
393 })
394 }
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use crate::compression::ChunkedArchiveOptions;
403
404 const TEST_DATA: &[u8] = b"hello world this is some test data to compress and decompress";
405
406 #[test]
407 fn test_zstd_roundtrip() {
408 let options =
409 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Zstd };
410 let mut compressor = options.compressor();
411 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
412
413 let mut decompressor = CompressionAlgorithm::Zstd.decompressor();
414 let decompressed = decompressor.decompress(&compressed, TEST_DATA.len(), 0).unwrap();
415
416 assert_eq!(decompressed, TEST_DATA);
417 }
418
419 #[test]
420 fn test_lz4_roundtrip() {
421 let options =
422 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
423 let mut compressor = options.compressor();
424 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
425
426 let mut decompressor = CompressionAlgorithm::Lz4.decompressor();
427 let decompressed = decompressor.decompress(&compressed, TEST_DATA.len(), 0).unwrap();
428
429 assert_eq!(decompressed, TEST_DATA);
430 }
431
432 #[test]
433 fn test_thread_local_zstd_roundtrip() {
434 let options =
435 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Zstd };
436 let compressor = options.thread_local_compressor();
437 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
438
439 let decompressor = CompressionAlgorithm::Zstd.thread_local_decompressor();
440 let decompressed = decompressor.decompress(&compressed, TEST_DATA.len(), 0).unwrap();
441
442 assert_eq!(decompressed, TEST_DATA);
443 }
444
445 #[test]
446 fn test_thread_local_lz4_roundtrip() {
447 let options =
448 ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
449 let compressor = options.thread_local_compressor();
450 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
451
452 let decompressor = CompressionAlgorithm::Lz4.thread_local_decompressor();
453 let decompressed = decompressor.decompress(&compressed, TEST_DATA.len(), 0).unwrap();
454
455 assert_eq!(decompressed, TEST_DATA);
456 }
457
458 #[test]
459 fn test_decompress_into() {
460 let options = ChunkedArchiveOptions::V2 {
461 minimum_chunk_size: 0,
462 chunk_alignment: 0,
463 compression_level: 1,
464 };
465 let mut compressor = options.compressor();
466 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
467
468 let mut decompressor = CompressionAlgorithm::Zstd.decompressor();
469 let mut buffer = vec![0u8; TEST_DATA.len()];
470 let len = decompressor.decompress_into(&compressed, &mut buffer, 0).unwrap();
471
472 assert_eq!(len, TEST_DATA.len());
473 assert_eq!(buffer, TEST_DATA);
474 }
475
476 #[test]
477 fn test_algorithm_conversion() {
478 assert_eq!(u8::from(CompressionAlgorithm::Zstd), 0);
479 assert_eq!(u8::from(CompressionAlgorithm::Lz4), 1);
480
481 assert_eq!(CompressionAlgorithm::try_from(0).unwrap(), CompressionAlgorithm::Zstd);
482 assert_eq!(CompressionAlgorithm::try_from(1).unwrap(), CompressionAlgorithm::Lz4);
483 assert!(CompressionAlgorithm::try_from(2).is_err());
484 }
485
486 #[test]
487 fn test_decompress_into_ptr() {
488 for algorithm in [CompressionAlgorithm::Zstd, CompressionAlgorithm::Lz4] {
489 let options = ChunkedArchiveOptions::V3 { compression_algorithm: algorithm };
490 let compressor = options.thread_local_compressor();
491 let compressed = compressor.compress(TEST_DATA, 0).unwrap();
492
493 let decompressor = algorithm.thread_local_decompressor();
494 let mut buffer = vec![0u8; TEST_DATA.len()];
495 let len = decompressor
496 .decompress_into(PtrByteSlice::from(compressed.as_slice()), &mut buffer, 0)
497 .unwrap();
498
499 assert_eq!(len, TEST_DATA.len());
500 assert_eq!(buffer, TEST_DATA);
501 }
502 }
503}