1pub mod blosclz;
11
12use crate::b2nd::B2ndMeta;
13use crate::constants::*;
14use std::cell::RefCell;
15use std::collections::HashMap;
16use std::ffi::{c_char, c_void, CStr};
17use std::sync::{OnceLock, RwLock};
18#[cfg(feature = "plugin-zfp")]
19use zfp_rs::{
20 types::ZFP_MAX_PREC, ZfpBitStream, ZfpConfig, ZfpDimensionality, ZfpField, ZfpFieldMut,
21 ZfpScalarType, ZfpStreamAlignment,
22};
23use zstd_pure_rs::common::error::{ErrorCode, ERROR};
24use zstd_pure_rs::common::xxhash::XXH64_state_t;
25use zstd_pure_rs::decompress::zstd_ddict::{
26 ZSTD_DDict, ZSTD_DDict_dictBuffer, ZSTD_DDict_dictContent, ZSTD_DDict_entropyPresent,
27 ZSTD_createDDict, ZSTD_getDictID_fromDDict,
28};
29use zstd_pure_rs::decompress::zstd_decompress::{
30 ZSTD_decompressFrame_withOpStart, ZSTD_loadDEntropy,
31};
32use zstd_pure_rs::decompress::zstd_decompress_block::{ZSTD_DCtx, ZSTD_decoder_entropy_rep};
33use zstd_pure_rs::prelude::*;
34
35pub type Blosc2PrefilterCb = Option<unsafe extern "C" fn(params: *mut c_void) -> i32>;
37
38pub type Blosc2PostfilterCb = Option<unsafe extern "C" fn(params: *mut c_void) -> i32>;
40
41pub type CodecCompressFn = fn(clevel: u8, meta: u8, src: &[u8], dest: &mut [u8]) -> i32;
44pub type CodecDecompressFn = fn(meta: u8, src: &[u8], dest: &mut [u8]) -> i32;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[repr(i32)]
51pub enum PluginCallbackStatus {
52 Success = 0,
53 Failure = 1,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct CodecCParamsContext {
59 pub compcode: u8,
60 pub compcode_meta: u8,
61 pub clevel: u8,
62 pub use_dict: i32,
63 pub typesize: i32,
64 pub blocksize: i32,
65 pub splitmode: i32,
66 pub filters: [u8; BLOSC2_MAX_FILTERS],
67 pub filters_meta: [u8; BLOSC2_MAX_FILTERS],
68 pub nthreads: i16,
69 pub nchunk: i64,
70 pub user_data: usize,
71 pub instr_codec: bool,
72 pub codec_params: usize,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct CodecDParamsContext {
78 pub nthreads: i16,
79 pub typesize: i32,
80 pub nchunk: i64,
81 pub user_data: usize,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct CodecChunkContext {
87 pub schunk: usize,
88 pub nchunk: i64,
89 pub nblock: i32,
90 pub chunk_source: usize,
91 pub block_offset: usize,
92 pub blocksize: usize,
93 pub bsize: usize,
94}
95
96#[derive(Debug, Clone, Copy)]
98pub struct CodecCallbackContext<'a> {
99 pub compcode: u8,
100 pub complib: Option<u8>,
101 pub meta: u8,
102 pub clevel: u8,
103 pub cparams: Option<&'a CodecCParamsContext>,
104 pub dparams: Option<&'a CodecDParamsContext>,
105 pub chunk: CodecChunkContext,
106 pub b2nd_metalayer: Option<&'a [u8]>,
107 pub user_data: usize,
108}
109
110#[derive(Clone, Copy)]
112#[repr(C)]
113pub struct Blosc2CParams {
114 pub compcode: u8,
115 pub compcode_meta: u8,
116 pub clevel: u8,
117 pub use_dict: i32,
118 pub typesize: i32,
119 pub nthreads: i16,
120 pub blocksize: i32,
121 pub splitmode: i32,
122 pub schunk: *mut c_void,
123 pub filters: [u8; BLOSC2_MAX_FILTERS],
124 pub filters_meta: [u8; BLOSC2_MAX_FILTERS],
125 pub prefilter: Blosc2PrefilterCb,
126 pub preparams: *mut c_void,
127 pub tuner_params: *mut c_void,
128 pub tuner_id: i32,
129 pub instr_codec: bool,
130 pub codec_params: *mut c_void,
131 pub filter_params: [*mut c_void; BLOSC2_MAX_FILTERS],
132}
133
134impl Blosc2CParams {
135 fn from_context(ctx: &CodecCParamsContext, schunk: usize) -> Self {
136 Self {
137 compcode: ctx.compcode,
138 compcode_meta: ctx.compcode_meta,
139 clevel: ctx.clevel,
140 use_dict: ctx.use_dict,
141 typesize: ctx.typesize,
142 nthreads: ctx.nthreads,
143 blocksize: ctx.blocksize,
144 splitmode: ctx.splitmode,
145 schunk: schunk as *mut c_void,
146 filters: ctx.filters,
147 filters_meta: ctx.filters_meta,
148 prefilter: None,
149 preparams: std::ptr::null_mut(),
150 tuner_params: std::ptr::null_mut(),
151 tuner_id: 0,
152 instr_codec: ctx.instr_codec,
153 codec_params: ctx.codec_params as *mut c_void,
154 filter_params: [std::ptr::null_mut(); BLOSC2_MAX_FILTERS],
155 }
156 }
157
158 fn from_pipeline(ctx: &CodecCallbackContext<'_>) -> Self {
159 ctx.cparams.map_or_else(
160 || Self {
161 compcode: ctx.compcode,
162 compcode_meta: ctx.meta,
163 clevel: ctx.clevel,
164 use_dict: 0,
165 typesize: 8,
166 nthreads: 1,
167 blocksize: 0,
168 splitmode: BLOSC_FORWARD_COMPAT_SPLIT,
169 schunk: ctx.chunk.schunk as *mut c_void,
170 filters: [
171 BLOSC_NOFILTER,
172 BLOSC_NOFILTER,
173 BLOSC_NOFILTER,
174 BLOSC_NOFILTER,
175 BLOSC_NOFILTER,
176 BLOSC_SHUFFLE,
177 ],
178 filters_meta: [0; BLOSC2_MAX_FILTERS],
179 prefilter: None,
180 preparams: std::ptr::null_mut(),
181 tuner_params: std::ptr::null_mut(),
182 tuner_id: 0,
183 instr_codec: false,
184 codec_params: std::ptr::null_mut(),
185 filter_params: [std::ptr::null_mut(); BLOSC2_MAX_FILTERS],
186 },
187 |cparams| Self::from_context(cparams, ctx.chunk.schunk),
188 )
189 }
190}
191
192#[derive(Clone, Copy)]
194#[repr(C)]
195pub struct Blosc2DParams {
196 pub nthreads: i16,
197 pub schunk: *mut c_void,
198 pub postfilter: Blosc2PostfilterCb,
199 pub postparams: *mut c_void,
200 pub typesize: i32,
201}
202
203impl Blosc2DParams {
204 fn from_context(ctx: &CodecDParamsContext, schunk: usize) -> Self {
205 Self {
206 nthreads: ctx.nthreads,
207 schunk: schunk as *mut c_void,
208 postfilter: None,
209 postparams: std::ptr::null_mut(),
210 typesize: ctx.typesize,
211 }
212 }
213
214 fn from_pipeline(ctx: &CodecCallbackContext<'_>) -> Self {
215 ctx.dparams.map_or_else(
216 || Self {
217 nthreads: 1,
218 schunk: ctx.chunk.schunk as *mut c_void,
219 postfilter: None,
220 postparams: std::ptr::null_mut(),
221 typesize: 8,
222 },
223 |dparams| Self::from_context(dparams, ctx.chunk.schunk),
224 )
225 }
226}
227
228pub type ContextCodecCompressFn =
230 for<'a> fn(&mut CodecCallbackContext<'a>, src: &[u8], dest: &mut [u8]) -> i32;
231pub type ContextCodecDecompressFn =
233 for<'a> fn(&mut CodecCallbackContext<'a>, src: &[u8], dest: &mut [u8]) -> i32;
234
235pub type Blosc2CodecEncoderCb = unsafe extern "C" fn(
237 input: *const u8,
238 input_len: i32,
239 output: *mut u8,
240 output_len: i32,
241 meta: u8,
242 cparams: *mut Blosc2CParams,
243 chunk: *const c_void,
244) -> i32;
245
246pub type Blosc2CodecDecoderCb = unsafe extern "C" fn(
248 input: *const u8,
249 input_len: i32,
250 output: *mut u8,
251 output_len: i32,
252 meta: u8,
253 dparams: *mut Blosc2DParams,
254 chunk: *const c_void,
255) -> i32;
256
257#[derive(Clone, Copy)]
259pub struct Blosc2Codec {
260 pub compcode: u8,
261 pub compname: &'static str,
262 pub complib: u8,
263 pub version: u8,
264 pub encoder: CodecCompressFn,
265 pub decoder: CodecDecompressFn,
266}
267
268#[derive(Clone, Copy)]
270#[repr(C)]
271pub struct Blosc2CodecAbi {
272 pub compcode: u8,
273 pub compname: *const c_char,
274 pub complib: u8,
275 pub version: u8,
276 pub encoder: Option<Blosc2CodecEncoderCb>,
277 pub decoder: Option<Blosc2CodecDecoderCb>,
278}
279
280#[derive(Clone, Copy)]
281struct UserCodec {
282 name: Option<&'static str>,
283 complib: Option<u8>,
284 version: Option<u8>,
285 compress: UserCodecCompress,
286 decompress: UserCodecDecompress,
287}
288
289#[derive(Clone, Copy)]
290enum UserCodecCompress {
291 Legacy(CodecCompressFn),
292 Context(ContextCodecCompressFn),
293 CAbi(Option<Blosc2CodecEncoderCb>),
294}
295
296impl UserCodecCompress {
297 fn same_callback(self, other: Self) -> bool {
298 match (self, other) {
299 (Self::Legacy(a), Self::Legacy(b)) => a as usize == b as usize,
300 (Self::Context(a), Self::Context(b)) => a as usize == b as usize,
301 (Self::CAbi(a), Self::CAbi(b)) => {
302 a.map(|callback| callback as usize) == b.map(|callback| callback as usize)
303 }
304 _ => false,
305 }
306 }
307
308 fn run(
309 self,
310 ctx: &mut CodecCallbackContext<'_>,
311 clevel: u8,
312 meta: u8,
313 src: &[u8],
314 dest: &mut [u8],
315 ) -> i32 {
316 match self {
317 Self::Legacy(callback) => callback(clevel, meta, src, dest),
318 Self::Context(callback) => callback(ctx, src, dest),
319 Self::CAbi(Some(callback)) => {
320 let Ok(input_len) = i32::try_from(src.len()) else {
321 return BLOSC2_ERROR_2GB_LIMIT;
322 };
323 let Ok(output_len) = i32::try_from(dest.len()) else {
324 return BLOSC2_ERROR_2GB_LIMIT;
325 };
326 let mut cparams = Blosc2CParams::from_pipeline(ctx);
327 let chunk = codec_chunk_arg(src.as_ptr(), ctx);
328 unsafe {
329 callback(
330 src.as_ptr(),
331 input_len,
332 dest.as_mut_ptr(),
333 output_len,
334 meta,
335 &mut cparams,
336 chunk,
337 )
338 }
339 }
340 Self::CAbi(None) => missing_dynamic_codec_callback(),
341 }
342 }
343}
344
345#[derive(Clone, Copy)]
346enum UserCodecDecompress {
347 Legacy(CodecDecompressFn),
348 Context(ContextCodecDecompressFn),
349 CAbi(Option<Blosc2CodecDecoderCb>),
350}
351
352impl UserCodecDecompress {
353 fn same_callback(self, other: Self) -> bool {
354 match (self, other) {
355 (Self::Legacy(a), Self::Legacy(b)) => a as usize == b as usize,
356 (Self::Context(a), Self::Context(b)) => a as usize == b as usize,
357 (Self::CAbi(a), Self::CAbi(b)) => {
358 a.map(|callback| callback as usize) == b.map(|callback| callback as usize)
359 }
360 _ => false,
361 }
362 }
363
364 fn run(self, ctx: &mut CodecCallbackContext<'_>, meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
365 match self {
366 Self::Legacy(callback) => callback(meta, src, dest),
367 Self::Context(callback) => callback(ctx, src, dest),
368 Self::CAbi(Some(callback)) => {
369 let Ok(input_len) = i32::try_from(src.len()) else {
370 return BLOSC2_ERROR_DATA;
371 };
372 let Ok(output_len) = i32::try_from(dest.len()) else {
373 return BLOSC2_ERROR_DATA;
374 };
375 let mut dparams = Blosc2DParams::from_pipeline(ctx);
376 let chunk = codec_chunk_arg(src.as_ptr(), ctx);
377 unsafe {
378 callback(
379 src.as_ptr(),
380 input_len,
381 dest.as_mut_ptr(),
382 output_len,
383 meta,
384 &mut dparams,
385 chunk,
386 )
387 }
388 }
389 Self::CAbi(None) => missing_dynamic_codec_callback(),
390 }
391 }
392}
393
394fn missing_dynamic_codec_callback() -> i32 {
395 BLOSC2_ERROR_CODEC_SUPPORT
398}
399
400fn codec_chunk_arg(block_ptr: *const u8, ctx: &CodecCallbackContext<'_>) -> *const c_void {
401 if ctx.chunk.chunk_source != 0 {
402 ctx.chunk.chunk_source as *const c_void
403 } else {
404 (block_ptr as usize)
405 .checked_sub(ctx.chunk.block_offset)
406 .map_or(std::ptr::null(), |addr| addr as *const c_void)
407 }
408}
409
410#[derive(Clone, Copy)]
411struct KnownGlobalCodec {
412 compcode: u8,
413 name: &'static str,
414 version: u8,
415}
416
417const KNOWN_GLOBAL_CODECS: &[KnownGlobalCodec] = &[
418 KnownGlobalCodec {
419 compcode: BLOSC_CODEC_NDLZ,
420 name: "ndlz",
421 version: 1,
422 },
423 KnownGlobalCodec {
424 compcode: BLOSC_CODEC_ZFP_FIXED_ACCURACY,
425 name: "zfp_acc",
426 version: 1,
427 },
428 KnownGlobalCodec {
429 compcode: BLOSC_CODEC_ZFP_FIXED_PRECISION,
430 name: "zfp_prec",
431 version: 1,
432 },
433 KnownGlobalCodec {
434 compcode: BLOSC_CODEC_ZFP_FIXED_RATE,
435 name: "zfp_rate",
436 version: 1,
437 },
438 KnownGlobalCodec {
439 compcode: BLOSC_CODEC_OPENHTJ2K,
440 name: "openhtj2k",
441 version: 1,
442 },
443 KnownGlobalCodec {
444 compcode: BLOSC_CODEC_GROK,
445 name: "grok",
446 version: 1,
447 },
448 KnownGlobalCodec {
449 compcode: BLOSC_CODEC_OPENZL,
450 name: "openzl",
451 version: 1,
452 },
453];
454
455impl UserCodec {
456 fn same_callbacks(self, other: Self) -> bool {
457 self.name == other.name
458 && self.complib == other.complib
459 && self.version == other.version
460 && self.compress.same_callback(other.compress)
461 && self.decompress.same_callback(other.decompress)
462 }
463}
464
465fn known_global_codec_by_code(compcode: u8) -> Option<KnownGlobalCodec> {
466 KNOWN_GLOBAL_CODECS
467 .iter()
468 .copied()
469 .find(|codec| codec.compcode == compcode)
470}
471
472fn known_global_codec_by_name(name: &str) -> Option<KnownGlobalCodec> {
473 KNOWN_GLOBAL_CODECS
474 .iter()
475 .copied()
476 .find(|codec| codec.name == name)
477}
478
479fn builtin_complib_info(complib: u8) -> Option<(&'static str, &'static str)> {
480 match complib {
481 BLOSC_BLOSCLZ_LIB => Some((BLOSC_BLOSCLZ_LIBNAME, "2.5.3")),
482 BLOSC_LZ4_LIB => Some((BLOSC_LZ4_LIBNAME, "1.10.0")),
483 BLOSC_ZLIB_LIB => Some((BLOSC_ZLIB_LIBNAME, "2.0.7")),
484 BLOSC_ZSTD_LIB => Some((BLOSC_ZSTD_LIBNAME, "1.5.7")),
485 _ => None,
486 }
487}
488
489pub fn is_known_global_codec(compcode: u8) -> bool {
494 known_global_codec_by_code(compcode).is_some()
495}
496
497pub fn is_known_zfp_codec(compcode: u8) -> bool {
499 matches!(
500 compcode,
501 BLOSC_CODEC_ZFP_FIXED_ACCURACY
502 | BLOSC_CODEC_ZFP_FIXED_PRECISION
503 | BLOSC_CODEC_ZFP_FIXED_RATE
504 )
505}
506
507pub fn is_static_global_codec_enabled(compcode: u8) -> bool {
508 (cfg!(feature = "plugin-ndlz") && compcode == BLOSC_CODEC_NDLZ)
509 || (cfg!(feature = "plugin-zfp") && is_known_zfp_codec(compcode))
510}
511
512static USER_CODECS: OnceLock<RwLock<HashMap<u8, UserCodec>>> = OnceLock::new();
513static USER_CODEC_ORDER: OnceLock<RwLock<Vec<u8>>> = OnceLock::new();
514
515struct Lz4Stream(*mut lz4_pure::sys::LZ4StreamEncode);
516
517impl Drop for Lz4Stream {
518 fn drop(&mut self) {
519 unsafe {
520 lz4_pure::sys::LZ4_freeStream(self.0);
521 }
522 }
523}
524
525struct Lz4hcStream(*mut lz4_pure::sys::LZ4StreamHC);
526
527impl Drop for Lz4hcStream {
528 fn drop(&mut self) {
529 unsafe {
530 lz4_pure::sys::LZ4_freeStreamHC(self.0);
531 }
532 }
533}
534
535thread_local! {
536 static LZ4_STREAM: RefCell<Option<Lz4Stream>> = const { RefCell::new(None) };
537 static LZ4HC_STREAM: RefCell<Option<Lz4hcStream>> = const { RefCell::new(None) };
538 static ZSTD_CCTX: RefCell<Option<Box<ZSTD_CCtx>>> = const { RefCell::new(None) };
539 static ZSTD_DICT_DCTX: RefCell<Box<ZSTD_DCtx>> = RefCell::new(ZSTD_createDCtx());
540 static ZSTD_DCTX: RefCell<(Box<ZSTD_DCtx>, ZSTD_decoder_entropy_rep, XXH64_state_t)> =
541 RefCell::new((
542 ZSTD_createDCtx(),
543 ZSTD_decoder_entropy_rep::default(),
544 XXH64_state_t::default(),
545 ));
546}
547
548fn user_codecs() -> &'static RwLock<HashMap<u8, UserCodec>> {
550 USER_CODECS.get_or_init(|| RwLock::new(HashMap::new()))
551}
552
553fn user_codec_order() -> &'static RwLock<Vec<u8>> {
554 USER_CODEC_ORDER.get_or_init(|| RwLock::new(Vec::new()))
555}
556
557fn remember_codec_order(compcode: u8) -> Result<(), &'static str> {
558 let mut order = user_codec_order()
559 .write()
560 .map_err(|_| "Codec registry poisoned")?;
561 if !order.contains(&compcode) {
562 order.push(compcode);
563 }
564 Ok(())
565}
566
567fn known_global_registration_status(
568 compcode: u8,
569 name: Option<&str>,
570 duplicate_error: &'static str,
571) -> Result<bool, &'static str> {
572 let Some(known) = known_global_codec_by_code(compcode) else {
573 return Ok(false);
574 };
575 if name != Some(known.name) {
576 return Err(duplicate_error);
577 }
578 Ok(true)
579}
580
581pub fn register_codec(
588 compcode: u8,
589 compress: CodecCompressFn,
590 decompress: CodecDecompressFn,
591) -> Result<(), &'static str> {
592 register_codec_impl(
593 compcode,
594 None,
595 compress,
596 decompress,
597 BLOSC2_USER_DEFINED_CODECS_START..=u8::MAX,
598 )
599}
600
601pub fn register_named_codec(
605 compcode: u8,
606 name: &'static str,
607 compress: CodecCompressFn,
608 decompress: CodecDecompressFn,
609) -> Result<(), &'static str> {
610 if name.is_empty() {
611 return Err("User-defined codec name cannot be empty");
612 }
613 register_codec_impl(
614 compcode,
615 Some(name),
616 compress,
617 decompress,
618 BLOSC2_USER_DEFINED_CODECS_START..=u8::MAX,
619 )
620}
621
622pub fn blosc2_register_codec(codec: &Blosc2Codec) -> i32 {
624 blosc2_register_codec_c(Some(codec))
625}
626
627pub fn blosc2_register_codec_c(codec: Option<&Blosc2Codec>) -> i32 {
629 let Some(codec) = codec else {
630 return BLOSC2_ERROR_INVALID_PARAM;
631 };
632 match register_blosc2_codec_impl(codec) {
633 Ok(()) => BLOSC2_ERROR_SUCCESS,
634 Err("Codec IDs must be >= 32")
635 | Err("User-defined codec IDs must be >= 160")
636 | Err("Codec ID outside allowed range")
637 | Err("User-defined codec ID already registered")
638 | Err("User-defined codec name already registered") => BLOSC2_ERROR_CODEC_PARAM,
639 Err("User-defined codec name cannot be empty") => BLOSC2_ERROR_INVALID_PARAM,
640 Err(_) => BLOSC2_ERROR_FAILURE,
641 }
642}
643
644pub fn blosc2_register_codec_abi(codec: *const Blosc2CodecAbi) -> i32 {
649 if codec.is_null() {
650 return BLOSC2_ERROR_INVALID_PARAM;
651 }
652 let codec = unsafe { &*codec };
653 match register_blosc2_codec_abi_impl(codec) {
654 Ok(()) => BLOSC2_ERROR_SUCCESS,
655 Err("Codec IDs must be >= 32")
656 | Err("User-defined codec IDs must be >= 160")
657 | Err("Codec ID outside allowed range")
658 | Err("User-defined codec ID already registered")
659 | Err("User-defined codec name already registered") => BLOSC2_ERROR_CODEC_PARAM,
660 Err("User-defined codec name cannot be empty") => BLOSC2_ERROR_INVALID_PARAM,
661 Err(_) => BLOSC2_ERROR_FAILURE,
662 }
663}
664
665fn register_blosc2_codec_impl(codec: &Blosc2Codec) -> Result<(), &'static str> {
666 if codec.compcode < BLOSC2_USER_DEFINED_CODECS_START {
667 return Err("User-defined codec IDs must be >= 160");
668 }
669 let mut codecs = user_codecs()
670 .write()
671 .map_err(|_| "Codec registry poisoned")?;
672
673 let registered = UserCodec {
674 name: Some(codec.compname),
675 complib: Some(codec.complib),
676 version: Some(codec.version),
677 compress: UserCodecCompress::Legacy(codec.encoder),
678 decompress: UserCodecDecompress::Legacy(codec.decoder),
679 };
680 if let Some(existing) = codecs.get(&codec.compcode) {
681 if existing.name == registered.name {
682 return Ok(());
683 }
684 return Err("User-defined codec ID already registered");
685 }
686 codecs.insert(codec.compcode, registered);
687 drop(codecs);
688 remember_codec_order(codec.compcode)?;
689 Ok(())
690}
691
692fn blosc2_codec_abi_name(name: *const c_char) -> Result<&'static str, &'static str> {
693 if name.is_null() {
694 return Err("User-defined codec name cannot be empty");
695 }
696 let c_name = unsafe { CStr::from_ptr(name) };
697 let name = match c_name.to_str() {
698 Ok(name) => name.to_owned(),
699 Err(_) => c_name
700 .to_bytes()
701 .iter()
702 .map(|&byte| char::from(byte))
703 .collect(),
704 };
705 Ok(Box::leak(name.into_boxed_str()))
706}
707
708fn register_blosc2_codec_abi_impl(codec: &Blosc2CodecAbi) -> Result<(), &'static str> {
709 if codec.compcode < BLOSC2_USER_DEFINED_CODECS_START {
710 return Err("User-defined codec IDs must be >= 160");
711 }
712 let name = blosc2_codec_abi_name(codec.compname)?;
713 let mut codecs = user_codecs()
714 .write()
715 .map_err(|_| "Codec registry poisoned")?;
716 let registered = UserCodec {
717 name: Some(name),
718 complib: Some(codec.complib),
719 version: Some(codec.version),
720 compress: UserCodecCompress::CAbi(codec.encoder),
721 decompress: UserCodecDecompress::CAbi(codec.decoder),
722 };
723 if let Some(existing) = codecs.get(&codec.compcode) {
724 if existing.name == Some(name) {
725 return Ok(());
726 }
727 return Err("User-defined codec ID already registered");
728 }
729 codecs.insert(codec.compcode, registered);
730 drop(codecs);
731 remember_codec_order(codec.compcode)?;
732 Ok(())
733}
734
735fn register_codec_impl(
736 compcode: u8,
737 name: Option<&'static str>,
738 compress: CodecCompressFn,
739 decompress: CodecDecompressFn,
740 allowed: std::ops::RangeInclusive<u8>,
741) -> Result<(), &'static str> {
742 if compcode < BLOSC2_USER_DEFINED_CODECS_START {
743 return Err("User-defined codec IDs must be >= 160");
744 }
745 if !allowed.contains(&compcode) {
746 return Err("Codec ID outside allowed range");
747 }
748 let mut codecs = user_codecs()
749 .write()
750 .map_err(|_| "Codec registry poisoned")?;
751 let codec = UserCodec {
752 name,
753 complib: None,
754 version: None,
755 compress: UserCodecCompress::Legacy(compress),
756 decompress: UserCodecDecompress::Legacy(decompress),
757 };
758 if let Some(existing) = codecs.get(&compcode) {
759 if name.is_some() && existing.name == name {
760 return Ok(());
761 }
762 if name.is_some() && existing.name != name {
763 return Err("User-defined codec ID already registered");
764 }
765 return if existing.same_callbacks(codec) {
766 Ok(())
767 } else {
768 Err("User-defined codec ID already registered")
769 };
770 }
771 codecs.insert(compcode, codec);
772 drop(codecs);
773 remember_codec_order(compcode)?;
774 Ok(())
775}
776
777pub fn register_global_codec(
784 compcode: u8,
785 compress: CodecCompressFn,
786 decompress: CodecDecompressFn,
787) -> Result<(), &'static str> {
788 register_global_codec_impl(compcode, None, None, None, compress, decompress)
789}
790
791pub fn register_named_global_codec(
793 compcode: u8,
794 name: &'static str,
795 compress: CodecCompressFn,
796 decompress: CodecDecompressFn,
797) -> Result<(), &'static str> {
798 if name.is_empty() {
799 return Err("Global plugin codec name cannot be empty");
800 }
801 register_global_codec_impl(compcode, Some(name), None, None, compress, decompress)
802}
803
804pub fn register_global_codec_with_metadata(
809 compcode: u8,
810 name: &'static str,
811 complib: u8,
812 version: u8,
813 compress: CodecCompressFn,
814 decompress: CodecDecompressFn,
815) -> Result<(), &'static str> {
816 if name.is_empty() {
817 return Err("Global plugin codec name cannot be empty");
818 }
819 register_global_codec_impl(
820 compcode,
821 Some(name),
822 Some(complib),
823 Some(version),
824 compress,
825 decompress,
826 )
827}
828
829pub fn register_private_codec(
834 compcode: u8,
835 compress: CodecCompressFn,
836 decompress: CodecDecompressFn,
837) -> Result<(), &'static str> {
838 register_private_codec_impl(compcode, None, None, None, compress, decompress)
839}
840
841pub fn register_named_private_codec(
843 compcode: u8,
844 name: &'static str,
845 compress: CodecCompressFn,
846 decompress: CodecDecompressFn,
847) -> Result<(), &'static str> {
848 if name.is_empty() {
849 return Err("Private codec name cannot be empty");
850 }
851 register_private_codec_impl(compcode, Some(name), None, None, compress, decompress)
852}
853
854pub fn register_private_codec_with_metadata(
856 compcode: u8,
857 name: &'static str,
858 complib: u8,
859 version: u8,
860 compress: CodecCompressFn,
861 decompress: CodecDecompressFn,
862) -> Result<(), &'static str> {
863 if name.is_empty() {
864 return Err("Private codec name cannot be empty");
865 }
866 register_private_codec_impl(
867 compcode,
868 Some(name),
869 Some(complib),
870 Some(version),
871 compress,
872 decompress,
873 )
874}
875
876pub fn register_context_codec(
878 compcode: u8,
879 compress: ContextCodecCompressFn,
880 decompress: ContextCodecDecompressFn,
881) -> Result<(), &'static str> {
882 register_context_codec_impl(
883 compcode,
884 None,
885 None,
886 None,
887 compress,
888 decompress,
889 BLOSC2_USER_DEFINED_CODECS_START..=u8::MAX,
890 "User-defined codec IDs must be >= 160",
891 "User-defined codec ID already registered",
892 )
893}
894
895pub fn register_global_context_codec(
897 compcode: u8,
898 compress: ContextCodecCompressFn,
899 decompress: ContextCodecDecompressFn,
900) -> Result<(), &'static str> {
901 register_context_codec_impl(
902 compcode,
903 None,
904 None,
905 None,
906 compress,
907 decompress,
908 BLOSC2_GLOBAL_REGISTERED_CODECS_START..=BLOSC2_GLOBAL_REGISTERED_CODECS_STOP,
909 "Global plugin codec IDs must be in 32..=159",
910 "Global plugin codec ID already registered",
911 )
912}
913
914pub fn register_global_context_codec_with_metadata(
916 compcode: u8,
917 name: &'static str,
918 complib: u8,
919 version: u8,
920 compress: ContextCodecCompressFn,
921 decompress: ContextCodecDecompressFn,
922) -> Result<(), &'static str> {
923 if name.is_empty() {
924 return Err("Global plugin codec name cannot be empty");
925 }
926 register_context_codec_impl(
927 compcode,
928 Some(name),
929 Some(complib),
930 Some(version),
931 compress,
932 decompress,
933 BLOSC2_GLOBAL_REGISTERED_CODECS_START..=BLOSC2_GLOBAL_REGISTERED_CODECS_STOP,
934 "Global plugin codec IDs must be in 32..=159",
935 "Global plugin codec ID already registered",
936 )
937}
938
939fn register_global_codec_impl(
940 compcode: u8,
941 name: Option<&'static str>,
942 complib: Option<u8>,
943 version: Option<u8>,
944 compress: CodecCompressFn,
945 decompress: CodecDecompressFn,
946) -> Result<(), &'static str> {
947 if !(BLOSC2_GLOBAL_REGISTERED_CODECS_START..=BLOSC2_GLOBAL_REGISTERED_CODECS_STOP)
948 .contains(&compcode)
949 {
950 return Err("Global plugin codec IDs must be in 32..=159");
951 }
952 if known_global_registration_status(
953 compcode,
954 name,
955 "Global plugin codec ID already registered",
956 )? {
957 return Ok(());
958 }
959 let mut codecs = user_codecs()
960 .write()
961 .map_err(|_| "Codec registry poisoned")?;
962 let codec = UserCodec {
963 name,
964 complib,
965 version,
966 compress: UserCodecCompress::Legacy(compress),
967 decompress: UserCodecDecompress::Legacy(decompress),
968 };
969 if let Some(existing) = codecs.get(&compcode) {
970 if name.is_some() && existing.name == name {
971 return Ok(());
972 }
973 return Err("Global plugin codec ID already registered");
974 }
975 codecs.insert(compcode, codec);
976 drop(codecs);
977 remember_codec_order(compcode)?;
978 Ok(())
979}
980
981fn register_private_codec_impl(
982 compcode: u8,
983 name: Option<&'static str>,
984 complib: Option<u8>,
985 version: Option<u8>,
986 compress: CodecCompressFn,
987 decompress: CodecDecompressFn,
988) -> Result<(), &'static str> {
989 if compcode < BLOSC2_GLOBAL_REGISTERED_CODECS_START {
990 return Err("Private codec IDs must be >= 32");
991 }
992 if known_global_registration_status(compcode, name, "Private codec ID already registered")? {
993 return Ok(());
994 }
995 let mut codecs = user_codecs()
996 .write()
997 .map_err(|_| "Codec registry poisoned")?;
998 let codec = UserCodec {
999 name,
1000 complib,
1001 version,
1002 compress: UserCodecCompress::Legacy(compress),
1003 decompress: UserCodecDecompress::Legacy(decompress),
1004 };
1005 if let Some(existing) = codecs.get(&compcode) {
1006 if name.is_some() && existing.name == name {
1007 return Ok(());
1008 }
1009 return Err("Private codec ID already registered");
1010 }
1011 codecs.insert(compcode, codec);
1012 drop(codecs);
1013 remember_codec_order(compcode)?;
1014 Ok(())
1015}
1016
1017#[allow(clippy::too_many_arguments)]
1018fn register_context_codec_impl(
1019 compcode: u8,
1020 name: Option<&'static str>,
1021 complib: Option<u8>,
1022 version: Option<u8>,
1023 compress: ContextCodecCompressFn,
1024 decompress: ContextCodecDecompressFn,
1025 allowed: std::ops::RangeInclusive<u8>,
1026 range_error: &'static str,
1027 duplicate_error: &'static str,
1028) -> Result<(), &'static str> {
1029 if !allowed.contains(&compcode) {
1030 return Err(range_error);
1031 }
1032 if known_global_registration_status(compcode, name, duplicate_error)? {
1033 return Ok(());
1034 }
1035 let mut codecs = user_codecs()
1036 .write()
1037 .map_err(|_| "Codec registry poisoned")?;
1038 let codec = UserCodec {
1039 name,
1040 complib,
1041 version,
1042 compress: UserCodecCompress::Context(compress),
1043 decompress: UserCodecDecompress::Context(decompress),
1044 };
1045 if let Some(existing) = codecs.get(&compcode) {
1046 if name.is_some() && existing.name == name {
1047 return Ok(());
1048 }
1049 return Err(duplicate_error);
1050 }
1051 codecs.insert(compcode, codec);
1052 drop(codecs);
1053 remember_codec_order(compcode)?;
1054 Ok(())
1055}
1056
1057pub fn is_registered_codec(compcode: u8) -> bool {
1060 known_global_codec_by_code(compcode).is_some()
1061 || user_codecs()
1062 .read()
1063 .is_ok_and(|codecs| codecs.contains_key(&compcode))
1064}
1065
1066pub fn registered_codec_name(compcode: u8) -> Option<&'static str> {
1068 if known_global_codec_by_code(compcode).is_some() {
1069 return known_global_codec_by_code(compcode).map(|codec| codec.name);
1070 }
1071 user_codecs()
1072 .read()
1073 .ok()
1074 .and_then(|codecs| codecs.get(&compcode).and_then(|codec| codec.name))
1075}
1076
1077pub fn registered_codec_complib_info(name: &str) -> Option<(u8, &'static str, &'static str)> {
1079 if let Some(codec) = known_global_codec_by_name(name) {
1080 return Some((codec.compcode, codec.name, "unknown"));
1081 }
1082 let codecs = user_codecs().read().ok()?;
1083 let order = user_codec_order().read().ok()?;
1084 order.iter().find_map(|code| {
1085 let codec = codecs.get(code)?;
1086 let codec_name = codec.name?;
1087 if codec_name != name {
1088 return None;
1089 }
1090 let complib = codec.complib?;
1091 if let Some((libname, version)) = builtin_complib_info(complib) {
1092 return Some((complib, libname, version));
1093 }
1094 let libname = order
1095 .iter()
1096 .find_map(|code| {
1097 let codec = codecs.get(code)?;
1098 (codec.complib == Some(complib)).then_some(codec.name?)
1099 })
1100 .unwrap_or(codec_name);
1101 codec.version.map(|_version| (complib, libname, "unknown"))
1102 })
1103}
1104
1105pub fn registered_codec_name_by_complib(complib: u8) -> Option<&'static str> {
1107 if let Some((libname, _version)) = builtin_complib_info(complib) {
1108 return Some(libname);
1109 }
1110 if let Some(codec) = known_global_codec_by_code(complib) {
1111 return Some(codec.name);
1112 }
1113 let codecs = user_codecs().read().ok()?;
1114 let order = user_codec_order().read().ok()?;
1115 order.iter().find_map(|code| {
1116 let codec = codecs.get(code)?;
1117 (codec.complib == Some(complib)).then_some(codec.name?)
1118 })
1119}
1120
1121pub fn registered_codec_version(compcode: u8) -> Option<u8> {
1123 if let Some(codec) = known_global_codec_by_code(compcode) {
1124 return Some(codec.version);
1125 }
1126 user_codecs()
1127 .read()
1128 .ok()
1129 .and_then(|codecs| codecs.get(&compcode).and_then(|codec| codec.version))
1130}
1131
1132pub fn registered_codec_code(name: &str) -> Option<u8> {
1134 if let Some(codec) = known_global_codec_by_name(name) {
1135 return Some(codec.compcode);
1136 }
1137 let codecs = user_codecs().read().ok()?;
1138 let order = user_codec_order().read().ok()?;
1139 order
1140 .iter()
1141 .find_map(|&code| (codecs.get(&code)?.name == Some(name)).then_some(code))
1142}
1143
1144pub fn codec_supports_dict(compcode: u8) -> bool {
1147 matches!(compcode, BLOSC_LZ4 | BLOSC_LZ4HC | BLOSC_ZSTD)
1148}
1149
1150pub fn compress_block(compcode: u8, clevel: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1158 compress_block_with_meta(compcode, clevel, 0, src, dest)
1159}
1160
1161pub fn compress_block_with_meta(
1166 compcode: u8,
1167 clevel: u8,
1168 meta: u8,
1169 src: &[u8],
1170 dest: &mut [u8],
1171) -> i32 {
1172 compress_block_with_context(compcode, clevel, meta, src, dest, None)
1173}
1174
1175pub fn compress_block_with_context(
1177 compcode: u8,
1178 clevel: u8,
1179 meta: u8,
1180 src: &[u8],
1181 dest: &mut [u8],
1182 context: Option<CodecCallbackContext<'_>>,
1183) -> i32 {
1184 match compcode {
1185 BLOSC_BLOSCLZ => blosclz::compress(clevel as i32, src, dest),
1186 BLOSC_LZ4 => lz4_compress(clevel, src, dest),
1187 BLOSC_LZ4HC => lz4hc_compress(clevel, src, dest),
1188 BLOSC_ZLIB => zlib_compress(src, dest, clevel),
1189 BLOSC_ZSTD => zstd_compress(src, dest, clevel),
1190 #[cfg(feature = "plugin-ndlz")]
1191 BLOSC_CODEC_NDLZ => compress_ndlz_plugin_block(meta, src, dest, context.as_ref()),
1192 #[cfg(feature = "plugin-zfp")]
1193 BLOSC_CODEC_ZFP_FIXED_ACCURACY
1194 | BLOSC_CODEC_ZFP_FIXED_PRECISION
1195 | BLOSC_CODEC_ZFP_FIXED_RATE => {
1196 compress_zfp_plugin_block(compcode, meta, src, dest, context.as_ref())
1197 }
1198 _ => match user_codecs()
1199 .read()
1200 .ok()
1201 .and_then(|codecs| codecs.get(&compcode).copied())
1202 {
1203 Some(codec) => {
1204 if matches!(codec.compress, UserCodecCompress::CAbi(None)) {
1205 return missing_dynamic_codec_callback();
1206 }
1207 let mut callback_context = context.unwrap_or(CodecCallbackContext {
1208 compcode,
1209 complib: codec.complib,
1210 meta,
1211 clevel,
1212 cparams: None,
1213 dparams: None,
1214 chunk: CodecChunkContext {
1215 schunk: 0,
1216 nchunk: -1,
1217 nblock: -1,
1218 chunk_source: 0,
1219 block_offset: 0,
1220 blocksize: src.len(),
1221 bsize: src.len(),
1222 },
1223 b2nd_metalayer: None,
1224 user_data: 0,
1225 });
1226 callback_context.compcode = compcode;
1227 callback_context.complib = codec.complib;
1228 callback_context.meta = meta;
1229 callback_context.clevel = clevel;
1230 let result = codec
1231 .compress
1232 .run(&mut callback_context, clevel, meta, src, dest);
1233 normalize_user_compress_result(result, dest.len())
1234 }
1235 None => BLOSC2_ERROR_CODEC_SUPPORT,
1236 },
1237 }
1238}
1239
1240pub fn compress_block_with_dict(
1245 compcode: u8,
1246 clevel: u8,
1247 src: &[u8],
1248 dest: &mut [u8],
1249 dict: &[u8],
1250) -> i32 {
1251 match compcode {
1252 BLOSC_LZ4 => lz4_compress_with_dict(clevel, src, dest, dict),
1253 BLOSC_LZ4HC => lz4hc_compress_with_dict(clevel, src, dest, dict),
1254 BLOSC_ZSTD => zstd_compress_with_dict(src, dest, clevel, dict),
1255 _ => compress_block(compcode, clevel, src, dest),
1256 }
1257}
1258
1259pub fn decompress_block(compcode: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1264 decompress_block_with_meta(compcode, 0, src, dest)
1265}
1266
1267pub fn decompress_block_with_meta(compcode: u8, meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1272 decompress_block_with_context(compcode, meta, src, dest, None)
1273}
1274
1275pub fn decompress_block_with_context(
1277 compcode: u8,
1278 meta: u8,
1279 src: &[u8],
1280 dest: &mut [u8],
1281 context: Option<CodecCallbackContext<'_>>,
1282) -> i32 {
1283 match compcode {
1284 BLOSC_BLOSCLZ => blosclz::decompress(src, dest),
1285 BLOSC_LZ4 | BLOSC_LZ4HC => lz4_decompress(src, dest),
1286 BLOSC_ZLIB => zlib_decompress(src, dest),
1287 BLOSC_ZSTD => zstd_decompress(src, dest),
1288 #[cfg(feature = "plugin-ndlz")]
1289 BLOSC_CODEC_NDLZ => decompress_ndlz_plugin_block(meta, src, dest),
1290 #[cfg(feature = "plugin-zfp")]
1291 BLOSC_CODEC_ZFP_FIXED_ACCURACY
1292 | BLOSC_CODEC_ZFP_FIXED_PRECISION
1293 | BLOSC_CODEC_ZFP_FIXED_RATE => {
1294 decompress_zfp_plugin_block(compcode, meta, src, dest, context.as_ref())
1295 }
1296 _ => match user_codecs()
1297 .read()
1298 .ok()
1299 .and_then(|codecs| codecs.get(&compcode).copied())
1300 {
1301 Some(codec) => {
1302 if matches!(codec.decompress, UserCodecDecompress::CAbi(None)) {
1303 return missing_dynamic_codec_callback();
1304 }
1305 if i32::try_from(dest.len()).is_err() {
1309 return BLOSC2_ERROR_DATA;
1310 }
1311 let mut callback_context = context.unwrap_or(CodecCallbackContext {
1312 compcode,
1313 complib: codec.complib,
1314 meta,
1315 clevel: 0,
1316 cparams: None,
1317 dparams: None,
1318 chunk: CodecChunkContext {
1319 schunk: 0,
1320 nchunk: -1,
1321 nblock: -1,
1322 chunk_source: 0,
1323 block_offset: 0,
1324 blocksize: dest.len(),
1325 bsize: dest.len(),
1326 },
1327 b2nd_metalayer: None,
1328 user_data: 0,
1329 });
1330 callback_context.compcode = compcode;
1331 callback_context.complib = codec.complib;
1332 callback_context.meta = meta;
1333 normalize_user_decompress_result(
1334 codec.decompress.run(&mut callback_context, meta, src, dest),
1335 dest.len(),
1336 )
1337 }
1338 None => BLOSC2_ERROR_CODEC_SUPPORT,
1339 },
1340 }
1341}
1342
1343fn normalize_user_compress_result(result: i32, dest_len: usize) -> i32 {
1344 if result > i32::try_from(dest_len).unwrap_or(i32::MAX) {
1345 BLOSC2_ERROR_WRITE_BUFFER
1346 } else if result < 0 {
1347 BLOSC2_ERROR_DATA
1348 } else {
1349 result
1350 }
1351}
1352
1353fn normalize_user_decompress_result(result: i32, dest_len: usize) -> i32 {
1354 let Ok(dest_len) = i32::try_from(dest_len) else {
1355 return BLOSC2_ERROR_DATA;
1356 };
1357 (result == dest_len)
1358 .then_some(result)
1359 .unwrap_or(BLOSC2_ERROR_DATA)
1360}
1361
1362pub fn decompress_block_with_dict(compcode: u8, src: &[u8], dest: &mut [u8], dict: &[u8]) -> i32 {
1366 match compcode {
1367 BLOSC_LZ4 | BLOSC_LZ4HC => lz4_decompress_with_dict(src, dest, dict),
1368 BLOSC_ZSTD => zstd_decompress_with_dict(src, dest, dict),
1369 _ => 0,
1370 }
1371}
1372
1373fn lz4_compress(clevel: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1375 use lz4_pure::sys::LZ4_compress_fast;
1376
1377 let Some(src_len) = len_as_c_int(src.len()) else {
1378 return 0;
1379 };
1380 let Some(dest_len) = len_as_c_int(dest.len()) else {
1381 return 0;
1382 };
1383 let accel = lz4_acceleration(clevel);
1384 unsafe {
1385 LZ4_compress_fast(
1386 src.as_ptr() as *const c_char,
1387 dest.as_mut_ptr() as *mut c_char,
1388 src_len,
1389 dest_len,
1390 accel,
1391 )
1392 }
1393}
1394
1395fn lz4_acceleration(clevel: u8) -> i32 {
1396 let _ = clevel;
1397 1
1400}
1401
1402fn lz4hc_compress(clevel: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1405 use lz4_pure::sys::LZ4_compress_HC;
1406
1407 if let Some(error) = lz4hc_2gb_limit_result(src.len()) {
1408 return error;
1409 }
1410 let Some(src_len) = len_as_c_int(src.len()) else {
1411 return 0;
1412 };
1413 let Some(dest_len) = len_as_c_int(dest.len()) else {
1414 return 0;
1415 };
1416
1417 unsafe {
1418 LZ4_compress_HC(
1419 src.as_ptr() as *const c_char,
1420 dest.as_mut_ptr() as *mut c_char,
1421 src_len,
1422 dest_len,
1423 i32::from(clevel),
1424 )
1425 }
1426}
1427
1428fn lz4hc_2gb_limit_result(src_len: usize) -> Option<i32> {
1429 (src_len > (2usize << 30)).then_some(BLOSC2_ERROR_2GB_LIMIT)
1430}
1431
1432fn lz4_decompress(src: &[u8], dest: &mut [u8]) -> i32 {
1435 match lz4_pure::block::decompress_to_buffer(src, Some(dest.len() as i32), dest) {
1436 Ok(n) if n == dest.len() => n as i32,
1437 Ok(_) | Err(_) => 0,
1438 }
1439}
1440
1441fn len_as_c_int(len: usize) -> Option<lz4_pure::sys::c_int> {
1444 lz4_pure::sys::c_int::try_from(len).ok()
1445}
1446
1447fn lz4_compress_with_dict(clevel: u8, src: &[u8], dest: &mut [u8], dict: &[u8]) -> i32 {
1449 use lz4_pure::sys::{c_char, LZ4_compress_fast_continue, LZ4_createStream, LZ4_loadDict};
1450
1451 let Some(src_len) = len_as_c_int(src.len()) else {
1452 return 0;
1453 };
1454 let Some(dest_len) = len_as_c_int(dest.len()) else {
1455 return 0;
1456 };
1457 let Some(dict_len) = len_as_c_int(dict.len()) else {
1458 return 0;
1459 };
1460 let accel = lz4_acceleration(clevel);
1461
1462 LZ4_STREAM.with(|slot| {
1463 let mut slot = slot.borrow_mut();
1464 if slot.is_none() {
1465 let stream = unsafe { LZ4_createStream() };
1466 if stream.is_null() {
1467 return 0;
1468 }
1469 *slot = Some(Lz4Stream(stream));
1470 }
1471 let stream = slot
1472 .as_mut()
1473 .map(|stream| stream.0)
1474 .unwrap_or(std::ptr::null_mut());
1475 if stream.is_null() {
1476 return 0;
1477 }
1478 unsafe {
1479 LZ4_loadDict(stream, dict.as_ptr() as *const c_char, dict_len);
1480 }
1481 let written = unsafe {
1482 LZ4_compress_fast_continue(
1483 stream,
1484 src.as_ptr() as *const c_char,
1485 dest.as_mut_ptr() as *mut c_char,
1486 src_len,
1487 dest_len,
1488 accel,
1489 )
1490 };
1491 written
1492 })
1493}
1494
1495fn lz4hc_compress_with_dict(clevel: u8, src: &[u8], dest: &mut [u8], dict: &[u8]) -> i32 {
1497 use lz4_pure::sys::{
1498 c_char, LZ4_compress_HC_continue, LZ4_createStreamHC, LZ4_loadDictHC,
1499 LZ4_resetStreamHC_fast,
1500 };
1501
1502 if let Some(error) = lz4hc_2gb_limit_result(src.len()) {
1503 return error;
1504 }
1505 let Some(src_len) = len_as_c_int(src.len()) else {
1506 return 0;
1507 };
1508 let Some(dest_len) = len_as_c_int(dest.len()) else {
1509 return 0;
1510 };
1511 let Some(dict_len) = len_as_c_int(dict.len()) else {
1512 return 0;
1513 };
1514
1515 LZ4HC_STREAM.with(|slot| {
1516 let mut slot = slot.borrow_mut();
1517 if slot.is_none() {
1518 let stream = unsafe { LZ4_createStreamHC() };
1519 if stream.is_null() {
1520 return 0;
1521 }
1522 *slot = Some(Lz4hcStream(stream));
1523 }
1524 let stream = slot
1525 .as_mut()
1526 .map(|stream| stream.0)
1527 .unwrap_or(std::ptr::null_mut());
1528 if stream.is_null() {
1529 return 0;
1530 }
1531 unsafe {
1532 LZ4_resetStreamHC_fast(stream, i32::from(clevel));
1533 LZ4_loadDictHC(stream, dict.as_ptr() as *const c_char, dict_len);
1534 }
1535 let written = unsafe {
1536 LZ4_compress_HC_continue(
1537 stream,
1538 src.as_ptr() as *const c_char,
1539 dest.as_mut_ptr() as *mut c_char,
1540 src_len,
1541 dest_len,
1542 )
1543 };
1544 written
1545 })
1546}
1547
1548fn lz4_decompress_with_dict(src: &[u8], dest: &mut [u8], dict: &[u8]) -> i32 {
1550 use lz4_pure::sys::{c_char, LZ4_decompress_safe_usingDict};
1551
1552 let Some(src_len) = len_as_c_int(src.len()) else {
1553 return 0;
1554 };
1555 let Some(dest_len) = len_as_c_int(dest.len()) else {
1556 return 0;
1557 };
1558 let Some(dict_len) = len_as_c_int(dict.len()) else {
1559 return 0;
1560 };
1561
1562 let written = unsafe {
1563 LZ4_decompress_safe_usingDict(
1564 src.as_ptr() as *const c_char,
1565 dest.as_mut_ptr() as *mut c_char,
1566 src_len,
1567 dest_len,
1568 dict.as_ptr() as *const c_char,
1569 dict_len,
1570 )
1571 };
1572 if written == dest_len {
1573 written
1574 } else {
1575 0
1576 }
1577}
1578
1579fn zlib_compress(src: &[u8], dest: &mut [u8], clevel: u8) -> i32 {
1583 use flate2::Compression;
1584
1585 let level = Compression::new(clevel as u32);
1587 let mut compress = flate2::Compress::new(level, true);
1588
1589 let status = compress.compress(src, dest, flate2::FlushCompress::Finish);
1590
1591 match status {
1592 Ok(flate2::Status::StreamEnd) => compress.total_out() as i32,
1593 Ok(flate2::Status::Ok | flate2::Status::BufError) => {
1594 0
1596 }
1597 Err(_) => 0,
1598 }
1599}
1600
1601fn zlib_decompress(src: &[u8], dest: &mut [u8]) -> i32 {
1603 use flate2::Decompress;
1604 use flate2::FlushDecompress;
1605
1606 let mut decompress = Decompress::new(true);
1607 match decompress.decompress(src, dest, FlushDecompress::Finish) {
1608 Ok(flate2::Status::StreamEnd) => decompress.total_out() as i32,
1609 Ok(_) => 0,
1610 Err(_) => 0,
1611 }
1612}
1613
1614fn blosc_clevel_to_zstd(clevel: u8) -> i32 {
1620 if clevel < 9 {
1621 (clevel as i32) * 2 - 1
1623 } else {
1624 22
1626 }
1627}
1628
1629fn zstd_normalize_result(n: Option<usize>) -> i32 {
1632 match n {
1633 Some(n) if !ERR_isError(n) && n <= i32::MAX as usize => n as i32,
1634 _ => 0,
1635 }
1636}
1637
1638fn zstd_compress(src: &[u8], dest: &mut [u8], clevel: u8) -> i32 {
1641 let n = ZSTD_CCTX.with(|slot| -> Option<usize> {
1642 let mut slot = slot.borrow_mut();
1643 if slot.is_none() {
1644 *slot = ZSTD_createCCtx();
1645 }
1646 let cctx = slot.as_mut()?;
1647 Some(ZSTD_compressCCtx(
1648 cctx,
1649 dest,
1650 src,
1651 blosc_clevel_to_zstd(clevel),
1652 ))
1653 });
1654 zstd_normalize_result(n)
1655}
1656
1657fn zstd_compress_with_dict(src: &[u8], dest: &mut [u8], _clevel: u8, dict: &[u8]) -> i32 {
1659 let n = ZSTD_CCTX.with(|slot| -> Option<usize> {
1660 let mut slot = slot.borrow_mut();
1661 if slot.is_none() {
1662 *slot = ZSTD_createCCtx();
1663 }
1664 let cctx = slot.as_mut()?;
1665 Some(ZSTD_compress_usingDict(cctx, dest, src, dict, 1))
1666 });
1667 zstd_normalize_result(n)
1668}
1669
1670fn zstd_decompress(src: &[u8], dest: &mut [u8]) -> i32 {
1672 let n = ZSTD_DCTX.with(|slot| {
1673 let mut slot = slot.borrow_mut();
1674 let (dctx, entropy_rep, xxh) = &mut *slot;
1675 *entropy_rep = ZSTD_decoder_entropy_rep::default();
1676 ZSTD_decompressDCtx(dctx, entropy_rep, xxh, dest, src)
1677 });
1678 if ERR_isError(n) {
1679 0
1680 } else {
1681 n as i32
1682 }
1683}
1684
1685fn zstd_decompress_with_dict(src: &[u8], dest: &mut [u8], dict: &[u8]) -> i32 {
1687 let n = ZSTD_DICT_DCTX.with(|slot| {
1688 let mut dctx = slot.borrow_mut();
1689 let Some(ddict) = ZSTD_createDDict(dict) else {
1690 return ERROR(ErrorCode::DictionaryCorrupted);
1691 };
1692 let n = zstd_decompress_using_ddict_with_active_entropy(&mut dctx, dest, src, &ddict);
1693 if ERR_isError(n) {
1694 if zstd_dict_has_magic(dict) {
1695 *dctx = ZSTD_createDCtx();
1696 let load = ZSTD_DCtx_loadDictionary(&mut dctx, dict);
1697 if !ERR_isError(load) {
1698 let mut entropy_rep = ZSTD_decoder_entropy_rep::default();
1699 let mut xxh = XXH64_state_t::default();
1700 let n = ZSTD_decompressDCtx(&mut dctx, &mut entropy_rep, &mut xxh, dest, src);
1701 if !ERR_isError(n) {
1702 return n;
1703 }
1704 }
1705 }
1706 let n = ZSTD_decompress_usingDict(&mut dctx, dest, src, dict);
1707 if ERR_isError(n) {
1708 let content = ZSTD_DDict_dictContent(&ddict);
1709 ZSTD_decompress_usingDict(&mut dctx, dest, src, content)
1710 } else {
1711 n
1712 }
1713 } else {
1714 n
1715 }
1716 });
1717 if ERR_isError(n) {
1718 0
1719 } else {
1720 n as i32
1721 }
1722}
1723
1724fn zstd_decompress_using_ddict_with_active_entropy(
1725 dctx: &mut ZSTD_DCtx,
1726 dest: &mut [u8],
1727 src: &[u8],
1728 ddict: &ZSTD_DDict,
1729) -> usize {
1730 let frame_dict_id = ZSTD_getDictID_fromFrame(src);
1731 let ddict_id = ZSTD_getDictID_fromDDict(ddict);
1732 if frame_dict_id != 0 && ddict_id != 0 && frame_dict_id != ddict_id {
1733 return ERROR(ErrorCode::DictionaryWrong);
1734 }
1735
1736 let declared = ZSTD_getFrameContentSize(src);
1737 let out_size = if declared == ZSTD_CONTENTSIZE_UNKNOWN || declared == ZSTD_CONTENTSIZE_ERROR {
1738 dest.len()
1739 } else {
1740 declared as usize
1741 };
1742 if out_size > dest.len() {
1743 return ERROR(ErrorCode::DstSizeTooSmall);
1744 }
1745
1746 let rc = ZSTD_decompressBegin(dctx);
1747 if ERR_isError(rc) {
1748 return rc;
1749 }
1750 let content = ZSTD_DDict_dictContent(ddict);
1751 dctx.stream_dict = content.to_vec();
1752 dctx.dictID = ddict_id;
1753 if ZSTD_DDict_entropyPresent(ddict) != 0 {
1754 let mut rep = [0u32; 3];
1755 let rc = ZSTD_loadDEntropy(dctx, &mut rep, ZSTD_DDict_dictBuffer(ddict));
1756 if ERR_isError(rc) {
1757 return rc;
1758 }
1759 dctx.ddict_rep = rep;
1760 dctx.litEntropy = 1;
1761 dctx.fseEntropy = 1;
1762 dctx.fse_ll_fresh = true;
1763 dctx.fse_of_fresh = true;
1764 dctx.fse_ml_fresh = true;
1765 dctx.ll_default_active = false;
1766 dctx.of_default_active = false;
1767 dctx.ml_default_active = false;
1768 } else {
1769 dctx.litEntropy = 0;
1770 dctx.fseEntropy = 0;
1771 dctx.fse_ll_fresh = false;
1772 dctx.fse_of_fresh = false;
1773 dctx.fse_ml_fresh = false;
1774 dctx.ll_default_active = true;
1775 dctx.of_default_active = true;
1776 dctx.ml_default_active = true;
1777 }
1778
1779 let mut combined = vec![0u8; content.len() + out_size];
1780 combined[..content.len()].copy_from_slice(content);
1781 let mut rep = ZSTD_decoder_entropy_rep {
1782 rep: dctx.ddict_rep,
1783 };
1784 let mut xxh = XXH64_state_t::default();
1785 let mut consumed = 0usize;
1786 let decoded = ZSTD_decompressFrame_withOpStart(
1787 dctx,
1788 &mut rep,
1789 &mut xxh,
1790 &mut combined,
1791 content.len(),
1792 src,
1793 &mut consumed,
1794 );
1795 if ERR_isError(decoded) {
1796 return decoded;
1797 }
1798 dest[..decoded].copy_from_slice(&combined[content.len()..content.len() + decoded]);
1799 decoded
1800}
1801
1802fn zstd_dict_has_magic(dict: &[u8]) -> bool {
1803 const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437;
1804 dict.get(..4)
1805 .and_then(|bytes| bytes.try_into().ok())
1806 .is_some_and(|bytes| u32::from_le_bytes(bytes) == ZSTD_MAGIC_DICTIONARY)
1807}
1808
1809fn read_u16_le(src: &[u8], pos: usize) -> Option<u16> {
1810 let bytes: [u8; 2] = src.get(pos..pos + 2)?.try_into().ok()?;
1811 Some(u16::from_le_bytes(bytes))
1812}
1813
1814fn read_i32_le(src: &[u8], pos: usize) -> Option<i32> {
1815 let bytes: [u8; 4] = src.get(pos..pos + 4)?.try_into().ok()?;
1816 Some(i32::from_le_bytes(bytes))
1817}
1818
1819fn xxh32_seed1(input: &[u8]) -> u32 {
1820 const PRIME32_1: u32 = 2_654_435_761;
1821 const PRIME32_2: u32 = 2_246_822_519;
1822 const PRIME32_3: u32 = 3_266_489_917;
1823 const PRIME32_4: u32 = 668_265_263;
1824 const PRIME32_5: u32 = 374_761_393;
1825
1826 fn round(acc: u32, lane: u32) -> u32 {
1827 acc.wrapping_add(lane.wrapping_mul(PRIME32_2))
1828 .rotate_left(13)
1829 .wrapping_mul(PRIME32_1)
1830 }
1831
1832 let mut pos = 0usize;
1833 let mut hash;
1834 if input.len() >= 16 {
1835 let mut v1 = 1u32.wrapping_add(PRIME32_1).wrapping_add(PRIME32_2);
1836 let mut v2 = 1u32.wrapping_add(PRIME32_2);
1837 let mut v3 = 1u32;
1838 let mut v4 = 1u32.wrapping_sub(PRIME32_1);
1839 while pos <= input.len() - 16 {
1840 v1 = round(
1841 v1,
1842 u32::from_le_bytes(input[pos..pos + 4].try_into().unwrap()),
1843 );
1844 pos += 4;
1845 v2 = round(
1846 v2,
1847 u32::from_le_bytes(input[pos..pos + 4].try_into().unwrap()),
1848 );
1849 pos += 4;
1850 v3 = round(
1851 v3,
1852 u32::from_le_bytes(input[pos..pos + 4].try_into().unwrap()),
1853 );
1854 pos += 4;
1855 v4 = round(
1856 v4,
1857 u32::from_le_bytes(input[pos..pos + 4].try_into().unwrap()),
1858 );
1859 pos += 4;
1860 }
1861 hash = v1
1862 .rotate_left(1)
1863 .wrapping_add(v2.rotate_left(7))
1864 .wrapping_add(v3.rotate_left(12))
1865 .wrapping_add(v4.rotate_left(18));
1866 } else {
1867 hash = 1u32.wrapping_add(PRIME32_5);
1868 }
1869
1870 hash = hash.wrapping_add(input.len() as u32);
1871 while pos + 4 <= input.len() {
1872 hash = hash
1873 .wrapping_add(
1874 u32::from_le_bytes(input[pos..pos + 4].try_into().unwrap()).wrapping_mul(PRIME32_3),
1875 )
1876 .rotate_left(17)
1877 .wrapping_mul(PRIME32_4);
1878 pos += 4;
1879 }
1880 while pos < input.len() {
1881 hash = hash
1882 .wrapping_add((input[pos] as u32).wrapping_mul(PRIME32_5))
1883 .rotate_left(11)
1884 .wrapping_mul(PRIME32_1);
1885 pos += 1;
1886 }
1887 hash ^= hash >> 15;
1888 hash = hash.wrapping_mul(PRIME32_2);
1889 hash ^= hash >> 13;
1890 hash = hash.wrapping_mul(PRIME32_3);
1891 hash ^ (hash >> 16)
1892}
1893
1894fn compress_ndlz_plugin_block(
1895 meta: u8,
1896 src: &[u8],
1897 dest: &mut [u8],
1898 context: Option<&CodecCallbackContext<'_>>,
1899) -> i32 {
1900 let Some(context) = context else {
1901 return 0;
1902 };
1903 let Some(b2nd_metalayer) = context.b2nd_metalayer else {
1904 return -1;
1905 };
1906 let Ok(b2nd_meta) = B2ndMeta::deserialize(b2nd_metalayer) else {
1907 return -1;
1908 };
1909 if b2nd_meta.shape.len() != 2 || b2nd_meta.blockshape.len() != 2 {
1910 return -1;
1911 }
1912 compress_ndlz_2d_block(
1913 meta,
1914 [b2nd_meta.blockshape[0], b2nd_meta.blockshape[1]],
1915 src,
1916 dest,
1917 )
1918}
1919
1920fn decompress_ndlz_plugin_block(meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
1921 match meta {
1922 4 | 8 => decompress_ndlz_cell_stream(meta as usize, src, dest),
1923 _ => -1,
1924 }
1925}
1926
1927#[cfg(feature = "plugin-zfp")]
1928fn compress_zfp_plugin_block(
1929 compcode: u8,
1930 meta: u8,
1931 src: &[u8],
1932 dest: &mut [u8],
1933 context: Option<&CodecCallbackContext<'_>>,
1934) -> i32 {
1935 let Some(context) = context else {
1936 return 0;
1937 };
1938 let Some(cparams) = context.cparams else {
1939 return 0;
1940 };
1941 let Ok(desc) = describe_zfp_block(context.b2nd_metalayer, cparams.typesize, src.len()) else {
1942 return BLOSC2_ERROR_FAILURE;
1943 };
1944 let config = zfp_config_for_mode(compcode, meta, desc.scalar_type, desc.dimensionality);
1945 let capacity = config
1946 .maximum_size(desc.scalar_type, &desc.dims[..desc.ndim])
1947 .max(dest.len());
1948 let field =
1949 unsafe { ZfpField::from_raw(src.as_ptr(), src.len(), desc.scalar_type, desc.dims, [0; 4]) };
1950 let mut stream = ZfpBitStream::new(capacity);
1951 let Ok(cbytes) = stream.compress(&config, &field) else {
1952 return 0;
1953 };
1954 if cbytes == 0 || cbytes >= src.len() {
1955 return 0;
1956 }
1957 if cbytes > dest.len() || i32::try_from(cbytes).is_err() {
1958 return 0;
1959 }
1960 dest[..cbytes].copy_from_slice(&stream.as_bytes()[..cbytes]);
1961 cbytes as i32
1962}
1963
1964#[cfg(feature = "plugin-zfp")]
1965fn decompress_zfp_plugin_block(
1966 compcode: u8,
1967 meta: u8,
1968 src: &[u8],
1969 dest: &mut [u8],
1970 context: Option<&CodecCallbackContext<'_>>,
1971) -> i32 {
1972 let Some(context) = context else {
1973 return 0;
1974 };
1975 let Some(dparams) = context.dparams else {
1976 return 0;
1977 };
1978 let Ok(desc) = describe_zfp_block(context.b2nd_metalayer, dparams.typesize, dest.len()) else {
1979 return BLOSC2_ERROR_FAILURE;
1980 };
1981 let config = zfp_config_for_mode(compcode, meta, desc.scalar_type, desc.dimensionality);
1982 let mut field = unsafe {
1983 ZfpFieldMut::from_raw(
1984 dest.as_mut_ptr(),
1985 dest.len(),
1986 desc.scalar_type,
1987 desc.dims,
1988 [0; 4],
1989 )
1990 };
1991 let mut stream = ZfpBitStream::from_bytes(src);
1992 match stream.decompress(&config, &mut field) {
1993 Ok(0) | Err(_) => 0,
1994 Ok(_) => i32::try_from(dest.len()).unwrap_or(BLOSC2_ERROR_DATA),
1995 }
1996}
1997
1998#[cfg(feature = "plugin-zfp")]
1999#[derive(Clone, Copy)]
2000struct ZfpBlockDescriptor {
2001 ndim: usize,
2002 dims: [usize; 4],
2003 scalar_type: ZfpScalarType,
2004 dimensionality: ZfpDimensionality,
2005}
2006
2007#[cfg(feature = "plugin-zfp")]
2008fn describe_zfp_block(
2009 b2nd_metalayer: Option<&[u8]>,
2010 typesize: i32,
2011 data_len: usize,
2012) -> Result<ZfpBlockDescriptor, ()> {
2013 let b2nd_metalayer = b2nd_metalayer.ok_or(())?;
2014 let b2nd_meta = B2ndMeta::deserialize(b2nd_metalayer).map_err(|_| ())?;
2015 let ndim = b2nd_meta.blockshape.len();
2016 let dimensionality = match ndim {
2017 1 => ZfpDimensionality::D1,
2018 2 => ZfpDimensionality::D2,
2019 3 => ZfpDimensionality::D3,
2020 4 => ZfpDimensionality::D4,
2021 _ => return Err(()),
2022 };
2023 let scalar_type = match typesize {
2024 4 => ZfpScalarType::Float,
2025 8 => ZfpScalarType::Double,
2026 _ => return Err(()),
2027 };
2028 let mut dims = [0usize; 4];
2029 let mut values = 1usize;
2030 for i in 0..ndim {
2031 let block_dim = b2nd_meta.blockshape[i];
2032 if block_dim < 4 {
2033 return Err(());
2034 }
2035 let dim = usize::try_from(block_dim).map_err(|_| ())?;
2036 dims[i] = usize::try_from(b2nd_meta.blockshape[ndim - 1 - i]).map_err(|_| ())?;
2037 values = values.checked_mul(dim).ok_or(())?;
2038 }
2039 let expected = values
2040 .checked_mul(usize::try_from(typesize).map_err(|_| ())?)
2041 .ok_or(())?;
2042 if data_len < expected {
2043 return Err(());
2044 }
2045 Ok(ZfpBlockDescriptor {
2046 ndim,
2047 dims,
2048 scalar_type,
2049 dimensionality,
2050 })
2051}
2052
2053#[cfg(feature = "plugin-zfp")]
2054fn zfp_config_for_mode(
2055 compcode: u8,
2056 meta: u8,
2057 scalar_type: ZfpScalarType,
2058 dimensionality: ZfpDimensionality,
2059) -> ZfpConfig {
2060 match compcode {
2061 BLOSC_CODEC_ZFP_FIXED_ACCURACY => ZfpConfig::fixed_accuracy(10f64.powi(meta as i8 as i32)),
2062 BLOSC_CODEC_ZFP_FIXED_PRECISION => {
2063 let offset = 2 * u32::from(dimensionality) + 3;
2064 ZfpConfig::fixed_precision((u32::from(meta) + offset).min(ZFP_MAX_PREC))
2065 }
2066 BLOSC_CODEC_ZFP_FIXED_RATE => {
2067 let ratio = f64::from(meta) / 100.0;
2068 let rate = ratio * scalar_type.size() as f64 * 8.0;
2069 ZfpConfig::fixed_rate(rate, scalar_type, dimensionality, ZfpStreamAlignment::None)
2070 }
2071 _ => ZfpConfig::new(),
2072 }
2073}
2074
2075const NDLZ_HASH_TABLE_SIZE: usize = 1 << 12;
2076
2077fn ndlz_rows_key(cell: &[u8], cell_shape: usize, rows: impl IntoIterator<Item = usize>) -> Vec<u8> {
2078 let mut key = Vec::new();
2079 for row in rows {
2080 let start = row * cell_shape;
2081 key.extend_from_slice(&cell[start..start + cell_shape]);
2082 }
2083 key
2084}
2085
2086fn ndlz_hash_bucket(bytes: &[u8]) -> usize {
2087 (xxh32_seed1(bytes) >> 20) as usize
2088}
2089
2090fn ndlz_table_match(
2091 table: &[usize; NDLZ_HASH_TABLE_SIZE],
2092 bucket: usize,
2093 bytes: &[u8],
2094 dest: &[u8],
2095) -> Option<usize> {
2096 let start = table[bucket];
2097 if start == 0 {
2098 return None;
2099 }
2100 let stored = dest.get(start..start + bytes.len())?;
2101 (stored == bytes).then_some(start)
2102}
2103
2104pub fn compress_ndlz_2d_block(meta: u8, blockshape: [i32; 2], src: &[u8], dest: &mut [u8]) -> i32 {
2109 let cell_shape = match meta {
2110 4 | 8 => meta as usize,
2111 _ => return -1,
2112 };
2113 if blockshape[0] < 0 || blockshape[1] < 0 {
2114 return -1;
2115 }
2116 let rows = blockshape[0] as usize;
2117 let cols = blockshape[1] as usize;
2118 let Some(expected_len) = rows.checked_mul(cols) else {
2119 return -1;
2120 };
2121 if src.len() != expected_len {
2122 return -1;
2123 }
2124 if dest.len() < 1 + 2 * std::mem::size_of::<i32>() {
2125 return -1;
2126 }
2127 if expected_len < cell_shape * cell_shape {
2128 return 0;
2129 }
2130 let min_output_len = 17 + expected_len / (cell_shape * cell_shape) * 2 - 2;
2131 if dest.len() < min_output_len {
2132 return 0;
2133 }
2134
2135 let stop_rows = rows.div_ceil(cell_shape);
2136 let stop_cols = cols.div_ceil(cell_shape);
2137 let mut op = 0usize;
2138 dest[op] = 2;
2139 op += 1;
2140 dest[op..op + 4].copy_from_slice(&blockshape[0].to_le_bytes());
2141 op += 4;
2142 dest[op..op + 4].copy_from_slice(&blockshape[1].to_le_bytes());
2143 op += 4;
2144 let mut tab_cell = [0usize; NDLZ_HASH_TABLE_SIZE];
2145 let mut tab_pair = [0usize; NDLZ_HASH_TABLE_SIZE];
2146 let mut tab_triple = [0usize; NDLZ_HASH_TABLE_SIZE];
2147
2148 for cell_row in 0..stop_rows {
2149 'cell_cols: for cell_col in 0..stop_cols {
2150 let pad_rows = if cell_row == stop_rows - 1 && !rows.is_multiple_of(cell_shape) {
2151 rows % cell_shape
2152 } else {
2153 cell_shape
2154 };
2155 let pad_cols = if cell_col == stop_cols - 1 && !cols.is_multiple_of(cell_shape) {
2156 cols % cell_shape
2157 } else {
2158 cell_shape
2159 };
2160 let orig = cell_row * cell_shape * cols + cell_col * cell_shape;
2161 let full_cell = pad_rows == cell_shape && pad_cols == cell_shape;
2162 if op + cell_shape * cell_shape + 1 > dest.len() {
2163 return 0;
2164 }
2165 let mut cell = Vec::new();
2166 if full_cell {
2167 cell.reserve_exact(cell_shape * cell_shape);
2168 for row in 0..cell_shape {
2169 let start = orig + row * cols;
2170 cell.extend_from_slice(&src[start..start + cell_shape]);
2171 }
2172 }
2173
2174 if full_cell && cell.iter().all(|&byte| byte == cell[0]) {
2175 if op + 2 > dest.len() {
2176 return 0;
2177 }
2178 dest[op] = 0x40;
2179 dest[op + 1] = cell[0];
2180 op += 2;
2181 if op > src.len() {
2182 return 0;
2183 }
2184 continue;
2185 }
2186
2187 let hash_cell = full_cell.then(|| ndlz_hash_bucket(&cell));
2188 let mut update_triple = [0usize; 6];
2189 let mut hash_triple = [0usize; 6];
2190 let mut update_pair = [0usize; 7];
2191 let mut hash_pair = [0usize; 7];
2192
2193 if full_cell {
2194 if let Some(literal_start) =
2195 ndlz_table_match(&tab_cell, hash_cell.unwrap(), &cell, dest)
2196 {
2197 let Some(offset) = op.checked_sub(literal_start) else {
2198 return -1;
2199 };
2200 if offset > 0 && offset < u16::MAX as usize {
2201 if op + 3 > dest.len() {
2202 return 0;
2203 }
2204 dest[op] = 0xc0;
2205 dest[op + 1..op + 3].copy_from_slice(&(offset as u16).to_le_bytes());
2206 op += 3;
2207 if op > src.len() {
2208 return 0;
2209 }
2210 continue;
2211 }
2212 }
2213 }
2214
2215 if full_cell {
2216 let token_pos = op;
2217 if cell_shape == 8 {
2218 for row in 0..=cell_shape - 3 {
2219 let key = ndlz_rows_key(&cell, cell_shape, row..row + 3);
2220 let bucket = ndlz_hash_bucket(&key);
2221 if let Some(ref_start) = ndlz_table_match(&tab_triple, bucket, &key, dest) {
2222 let Some(offset) = token_pos.checked_sub(ref_start) else {
2223 return -1;
2224 };
2225 let distance = token_pos + row * cell_shape - ref_start;
2226 if distance > 0 && distance < u16::MAX as usize {
2227 let literal_rows = cell_shape - 3;
2228 if op + 3 + literal_rows * cell_shape > dest.len() {
2229 return 0;
2230 }
2231 dest[op] = (21 << 3) | row as u8;
2232 dest[op + 1..op + 3]
2233 .copy_from_slice(&(offset as u16).to_le_bytes());
2234 op += 3;
2235 for literal_row in 0..cell_shape {
2236 if literal_row < row || literal_row >= row + 3 {
2237 let start = literal_row * cell_shape;
2238 dest[op..op + cell_shape]
2239 .copy_from_slice(&cell[start..start + cell_shape]);
2240 op += cell_shape;
2241 }
2242 }
2243 if op > src.len() {
2244 return 0;
2245 }
2246 continue 'cell_cols;
2247 }
2248 } else {
2249 update_triple[row] = token_pos + 1 + row * cell_shape;
2250 hash_triple[row] = bucket;
2251 }
2252 }
2253
2254 for row in 0..=cell_shape - 2 {
2255 let key = ndlz_rows_key(&cell, cell_shape, row..row + 2);
2256 let bucket = ndlz_hash_bucket(&key);
2257 if let Some(ref_start) = ndlz_table_match(&tab_pair, bucket, &key, dest) {
2258 let Some(offset) = token_pos.checked_sub(ref_start) else {
2259 return -1;
2260 };
2261 let distance = token_pos + row * cell_shape - ref_start;
2262 if distance > 0 && distance < u16::MAX as usize {
2263 let literal_rows = cell_shape - 2;
2264 if op + 3 + literal_rows * cell_shape > dest.len() {
2265 return 0;
2266 }
2267 dest[op] = (17 << 3) | row as u8;
2268 dest[op + 1..op + 3]
2269 .copy_from_slice(&(offset as u16).to_le_bytes());
2270 op += 3;
2271 for literal_row in 0..cell_shape {
2272 if literal_row < row || literal_row >= row + 2 {
2273 let start = literal_row * cell_shape;
2274 dest[op..op + cell_shape]
2275 .copy_from_slice(&cell[start..start + cell_shape]);
2276 op += cell_shape;
2277 }
2278 }
2279 if op > src.len() {
2280 return 0;
2281 }
2282 continue 'cell_cols;
2283 }
2284 } else {
2285 update_pair[row] = token_pos + 1 + row * cell_shape;
2286 hash_pair[row] = bucket;
2287 }
2288 }
2289 } else {
2290 for j in 1..cell_shape {
2291 let first_key = ndlz_rows_key(&cell, cell_shape, [0, j]);
2292 let first_bucket = ndlz_hash_bucket(&first_key);
2293 let Some(first_ref_start) =
2294 ndlz_table_match(&tab_pair, first_bucket, &first_key, dest)
2295 else {
2296 continue;
2297 };
2298 let Some(first_offset) = token_pos.checked_sub(first_ref_start) else {
2299 return -1;
2300 };
2301 if first_offset == 0 || first_offset >= u16::MAX as usize {
2302 continue;
2303 }
2304
2305 let mut remaining = (1..cell_shape).filter(|&row| row != j);
2306 let Some(l) = remaining.next() else {
2307 return -1;
2308 };
2309 let Some(m) = remaining.next() else {
2310 return -1;
2311 };
2312 let second_key = ndlz_rows_key(&cell, cell_shape, [l, m]);
2313 let second_bucket = ndlz_hash_bucket(&second_key);
2314 let Some(second_ref_start) =
2315 ndlz_table_match(&tab_pair, second_bucket, &second_key, dest)
2316 else {
2317 continue;
2318 };
2319 let Some(second_offset) = token_pos.checked_sub(second_ref_start) else {
2320 return -1;
2321 };
2322 let second_distance = token_pos + l * cell_shape - second_ref_start;
2323 if second_distance == 0 || second_distance >= u16::MAX as usize {
2324 continue;
2325 }
2326 if op + 5 > dest.len() {
2327 return 0;
2328 }
2329 dest[op] = (1 << 5) | ((j as u8) << 3);
2330 dest[op + 1..op + 3].copy_from_slice(&(first_offset as u16).to_le_bytes());
2331 dest[op + 3..op + 5].copy_from_slice(&(second_offset as u16).to_le_bytes());
2332 op += 5;
2333 if op > src.len() {
2334 return 0;
2335 }
2336 continue 'cell_cols;
2337 }
2338
2339 for rows in [[0usize, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]] {
2340 let key = ndlz_rows_key(&cell, cell_shape, rows);
2341 let bucket = ndlz_hash_bucket(&key);
2342 if let Some(ref_start) = ndlz_table_match(&tab_triple, bucket, &key, dest) {
2343 let Some(offset) = token_pos.checked_sub(ref_start) else {
2344 return -1;
2345 };
2346 let distance = token_pos + rows[0] * cell_shape - ref_start;
2347 if distance > 0 && distance < u16::MAX as usize {
2348 if op + 3 + cell_shape > dest.len() {
2349 return 0;
2350 }
2351 dest[op] = match rows {
2352 [1, 2, 3] => 7 << 5,
2353 [0, 1, 2] => (7 << 5) | (1 << 3),
2354 [0, 1, 3] => (7 << 5) | (2 << 3),
2355 [0, 2, 3] => (7 << 5) | (3 << 3),
2356 _ => unreachable!(),
2357 };
2358 dest[op + 1..op + 3]
2359 .copy_from_slice(&(offset as u16).to_le_bytes());
2360 op += 3;
2361 for literal_row in 0..cell_shape {
2362 if !rows.contains(&literal_row) {
2363 let start = literal_row * cell_shape;
2364 dest[op..op + cell_shape]
2365 .copy_from_slice(&cell[start..start + cell_shape]);
2366 op += cell_shape;
2367 break;
2368 }
2369 }
2370 if op > src.len() {
2371 return 0;
2372 }
2373 continue 'cell_cols;
2374 }
2375 } else if rows[1] - rows[0] == 1 && rows[2] - rows[1] == 1 {
2376 update_triple[rows[0]] = token_pos + 1 + rows[0] * cell_shape;
2377 hash_triple[rows[0]] = bucket;
2378 }
2379 }
2380
2381 for rows in [[0usize, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]] {
2382 let key = ndlz_rows_key(&cell, cell_shape, rows);
2383 let bucket = ndlz_hash_bucket(&key);
2384 if let Some(ref_start) = ndlz_table_match(&tab_pair, bucket, &key, dest) {
2385 let Some(offset) = token_pos.checked_sub(ref_start) else {
2386 return -1;
2387 };
2388 let distance = token_pos + rows[0] * cell_shape - ref_start;
2389 if distance > 0 && distance < u16::MAX as usize {
2390 if op + 3 + 2 * cell_shape > dest.len() {
2391 return 0;
2392 }
2393 dest[op] = if rows == [2, 3] {
2394 1 << 7
2395 } else {
2396 (1 << 7) | ((rows[0] as u8) << 5) | ((rows[1] as u8) << 3)
2397 };
2398 dest[op + 1..op + 3]
2399 .copy_from_slice(&(offset as u16).to_le_bytes());
2400 op += 3;
2401 for literal_row in 0..cell_shape {
2402 if !rows.contains(&literal_row) {
2403 let start = literal_row * cell_shape;
2404 dest[op..op + cell_shape]
2405 .copy_from_slice(&cell[start..start + cell_shape]);
2406 op += cell_shape;
2407 }
2408 }
2409 if op > src.len() {
2410 return 0;
2411 }
2412 continue 'cell_cols;
2413 }
2414 } else if rows[1] - rows[0] == 1 {
2415 update_pair[rows[0]] = token_pos + 1 + rows[0] * cell_shape;
2416 hash_pair[rows[0]] = bucket;
2417 }
2418 }
2419 }
2420 }
2421
2422 let literal_len = pad_rows * pad_cols;
2423 if op + 1 + literal_len > dest.len() {
2424 return 0;
2425 }
2426 dest[op] = 0;
2427 op += 1;
2428 let literal_start = op;
2429 for row in 0..pad_rows {
2430 let start = orig + row * cols;
2431 dest[op..op + pad_cols].copy_from_slice(&src[start..start + pad_cols]);
2432 op += pad_cols;
2433 }
2434 if full_cell {
2435 tab_cell[hash_cell.unwrap()] = literal_start;
2436 if update_triple[0] != 0 {
2437 let staged_triples = cell_shape - 2;
2438 for h in 0..staged_triples {
2439 tab_triple[hash_triple[h]] = update_triple[h];
2440 }
2441 }
2442 if update_pair[0] != 0 {
2443 let staged_pairs = cell_shape - 1;
2444 for h in 0..staged_pairs {
2445 tab_pair[hash_pair[h]] = update_pair[h];
2446 }
2447 }
2448 }
2449
2450 if op > src.len() {
2451 return 0;
2452 }
2453 }
2454 }
2455
2456 op as i32
2457}
2458
2459fn ndlz_copy_rows_from_stream(
2460 cell: &mut [u8],
2461 cell_shape: usize,
2462 src: &[u8],
2463 start: usize,
2464 rows: impl IntoIterator<Item = usize>,
2465) -> Option<()> {
2466 for (row_idx, row) in rows.into_iter().enumerate() {
2467 let src_start = start.checked_add(row_idx.checked_mul(cell_shape)?)?;
2468 let src_end = src_start.checked_add(cell_shape)?;
2469 let dst_start = row.checked_mul(cell_shape)?;
2470 let dst_end = dst_start.checked_add(cell_shape)?;
2471 cell.get_mut(dst_start..dst_end)?
2472 .copy_from_slice(src.get(src_start..src_end)?);
2473 }
2474 Some(())
2475}
2476
2477fn decompress_ndlz_cell_stream(cell_shape: usize, src: &[u8], dest: &mut [u8]) -> i32 {
2478 if src.len() < 8 {
2479 return 0;
2480 }
2481 if src.len() < 9 || src[0] != 2 {
2482 return -1;
2483 }
2484 let Some(rows) = read_i32_le(src, 1) else {
2485 return -1;
2486 };
2487 let Some(cols) = read_i32_le(src, 5) else {
2488 return -1;
2489 };
2490 if rows < 0 || cols < 0 {
2491 return -1;
2492 }
2493 let rows = rows as usize;
2494 let cols = cols as usize;
2495 let Some(expected_len) = rows.checked_mul(cols) else {
2496 return -1;
2497 };
2498 if expected_len > dest.len() {
2499 return 0;
2500 }
2501 dest[..expected_len].fill(0);
2502
2503 let cell_size = cell_shape * cell_shape;
2504 let stop_rows = rows.div_ceil(cell_shape);
2505 let stop_cols = cols.div_ceil(cell_shape);
2506 let mut ip = 9usize;
2507 let mut last_end = 0usize;
2508
2509 for cell_row in 0..stop_rows {
2510 for cell_col in 0..stop_cols {
2511 if ip >= src.len() {
2512 return -1;
2513 }
2514 let pad_rows = if cell_row == stop_rows - 1 && !rows.is_multiple_of(cell_shape) {
2515 rows % cell_shape
2516 } else {
2517 cell_shape
2518 };
2519 let pad_cols = if cell_col == stop_cols - 1 && !cols.is_multiple_of(cell_shape) {
2520 cols % cell_shape
2521 } else {
2522 cell_shape
2523 };
2524
2525 let token_pos = ip;
2526 let token = src[ip];
2527 ip += 1;
2528 let mut cell = vec![0u8; cell_size];
2529
2530 if token == 0 {
2531 let literal_len = pad_rows * pad_cols;
2532 let Some(literal) = src.get(ip..ip + literal_len) else {
2533 return -1;
2534 };
2535 for row in 0..pad_rows {
2536 let lit_start = row * pad_cols;
2537 let dst_start = row * cell_shape;
2538 cell[dst_start..dst_start + pad_cols]
2539 .copy_from_slice(&literal[lit_start..lit_start + pad_cols]);
2540 }
2541 ip += literal_len;
2542 } else if token == 0xc0 {
2543 let Some(offset) = read_u16_le(src, ip).map(usize::from) else {
2544 return -1;
2545 };
2546 let Some(ref_start) = ip.checked_sub(offset + 1) else {
2547 return -1;
2548 };
2549 if ndlz_copy_rows_from_stream(&mut cell, cell_shape, src, ref_start, 0..cell_shape)
2550 .is_none()
2551 {
2552 return -1;
2553 }
2554 ip += 2;
2555 } else if token == 0x40 {
2556 let Some(&value) = src.get(ip) else {
2557 return -1;
2558 };
2559 cell.fill(value);
2560 ip += 1;
2561 } else if cell_shape == 4 {
2562 if token >= 224 {
2563 let Some(offset) = read_u16_le(src, ip).map(|v| usize::from(v) + 3) else {
2564 return -1;
2565 };
2566 ip += 2;
2567 let (i, j, k) = if token >> 3 == 28 {
2568 (1, 2, 3)
2569 } else {
2570 let i = 0;
2571 if token >> 3 < 30 {
2572 (i, 1, 2)
2573 } else if token >> 3 == 30 {
2574 (i, 1, 3)
2575 } else {
2576 (i, 2, 3)
2577 }
2578 };
2579 let Some(ref_start) = ip.checked_sub(offset) else {
2580 return -1;
2581 };
2582 if ndlz_copy_rows_from_stream(&mut cell, cell_shape, src, ref_start, [i, j, k])
2583 .is_none()
2584 {
2585 return -1;
2586 }
2587 for row in 0..cell_shape {
2588 if row != i && row != j && row != k {
2589 let Some(literal) = src.get(ip..ip + cell_shape) else {
2590 return -1;
2591 };
2592 cell[row * cell_shape..(row + 1) * cell_shape].copy_from_slice(literal);
2593 ip += cell_shape;
2594 break;
2595 }
2596 }
2597 } else if (128..=191).contains(&token) {
2598 let Some(offset) = read_u16_le(src, ip).map(|v| usize::from(v) + 3) else {
2599 return -1;
2600 };
2601 ip += 2;
2602 let (i, j) = if token == 128 {
2603 (2, 3)
2604 } else {
2605 let i = ((token - 128) >> 5) as usize;
2606 let j = (((token - 128) >> 3) - ((i as u8) << 2)) as usize;
2607 (i, j)
2608 };
2609 let Some(ref_start) = ip.checked_sub(offset) else {
2610 return -1;
2611 };
2612 if ndlz_copy_rows_from_stream(&mut cell, cell_shape, src, ref_start, [i, j])
2613 .is_none()
2614 {
2615 return -1;
2616 }
2617 for row in 0..cell_shape {
2618 if row != i && row != j {
2619 let Some(literal) = src.get(ip..ip + cell_shape) else {
2620 return -1;
2621 };
2622 cell[row * cell_shape..(row + 1) * cell_shape].copy_from_slice(literal);
2623 ip += cell_shape;
2624 }
2625 }
2626 } else if (40..=63).contains(&token) {
2627 let Some(offset_1) = read_u16_le(src, ip).map(|v| usize::from(v) + 5) else {
2628 return -1;
2629 };
2630 ip += 2;
2631 let Some(offset_2) = read_u16_le(src, ip).map(|v| usize::from(v) + 5) else {
2632 return -1;
2633 };
2634 ip += 2;
2635 let i = 0usize;
2636 let j = ((token - 32) >> 3) as usize;
2637 let mut rest = (1..cell_shape).filter(|&row| row != j);
2638 let Some(l) = rest.next() else {
2639 return -1;
2640 };
2641 let Some(m) = rest.next() else {
2642 return -1;
2643 };
2644 let Some(ref_start_1) = ip.checked_sub(offset_1) else {
2645 return -1;
2646 };
2647 let Some(ref_start_2) = ip.checked_sub(offset_2) else {
2648 return -1;
2649 };
2650 if ndlz_copy_rows_from_stream(&mut cell, cell_shape, src, ref_start_1, [i, j])
2651 .is_none()
2652 {
2653 return -1;
2654 }
2655 if ndlz_copy_rows_from_stream(&mut cell, cell_shape, src, ref_start_2, [l, m])
2656 .is_none()
2657 {
2658 return -1;
2659 }
2660 } else {
2661 return -1;
2662 }
2663 } else {
2664 let match_type = token >> 3;
2665 if match_type == 21 || match_type == 17 {
2666 let row = (token & 7) as usize;
2667 let matched = if match_type == 21 { 3 } else { 2 };
2668 if row + matched > cell_shape {
2669 return -1;
2670 }
2671 let Some(offset) = read_u16_le(src, ip).map(usize::from) else {
2672 return -1;
2673 };
2674 ip += 2;
2675 let Some(ref_start) = ip.checked_sub(3 + offset) else {
2676 return -1;
2677 };
2678 if ndlz_copy_rows_from_stream(
2679 &mut cell,
2680 cell_shape,
2681 src,
2682 ref_start,
2683 row..row + matched,
2684 )
2685 .is_none()
2686 {
2687 return -1;
2688 }
2689 for literal_row in 0..cell_shape {
2690 if literal_row < row || literal_row >= row + matched {
2691 let Some(literal) = src.get(ip..ip + cell_shape) else {
2692 return -1;
2693 };
2694 cell[literal_row * cell_shape..(literal_row + 1) * cell_shape]
2695 .copy_from_slice(literal);
2696 ip += cell_shape;
2697 }
2698 }
2699 } else {
2700 return -1;
2701 }
2702 }
2703
2704 let orig = cell_row * cell_shape * cols + cell_col * cell_shape;
2705 for row in 0..pad_rows {
2706 let dst_start = orig + row * cols;
2707 let dst_end = dst_start + pad_cols;
2708 let src_start = row * cell_shape;
2709 dest[dst_start..dst_end].copy_from_slice(&cell[src_start..src_start + pad_cols]);
2710 last_end = dst_end;
2711 }
2712 if ip < token_pos || last_end > dest.len() {
2713 return -1;
2714 }
2715 }
2716 }
2717
2718 if last_end != expected_len {
2719 return -1;
2720 }
2721 expected_len as i32
2722}
2723
2724#[cfg(test)]
2725mod tests {
2726 use super::*;
2727 use std::sync::{
2728 atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicUsize, Ordering},
2729 Mutex,
2730 };
2731
2732 static C_ABI_TEST_LOCK: Mutex<()> = Mutex::new(());
2733 static C_ABI_COMPRESS_INPUT_LEN: AtomicI32 = AtomicI32::new(0);
2734 static C_ABI_COMPRESS_OUTPUT_LEN: AtomicI32 = AtomicI32::new(0);
2735 static C_ABI_COMPRESS_META: AtomicU8 = AtomicU8::new(0);
2736 static C_ABI_COMPRESS_CLEVEL: AtomicU8 = AtomicU8::new(0);
2737 static C_ABI_COMPRESS_COMP_META: AtomicU8 = AtomicU8::new(0);
2738 static C_ABI_COMPRESS_USE_DICT: AtomicI32 = AtomicI32::new(0);
2739 static C_ABI_COMPRESS_TYPESIZE: AtomicI32 = AtomicI32::new(0);
2740 static C_ABI_COMPRESS_BLOCKSIZE: AtomicI32 = AtomicI32::new(0);
2741 static C_ABI_COMPRESS_SPLITMODE: AtomicI32 = AtomicI32::new(0);
2742 static C_ABI_COMPRESS_FILTER_LAST: AtomicU8 = AtomicU8::new(0);
2743 static C_ABI_COMPRESS_INPUT_PTR: AtomicUsize = AtomicUsize::new(0);
2744 static C_ABI_COMPRESS_SCHUNK: AtomicUsize = AtomicUsize::new(0);
2745 static C_ABI_COMPRESS_CHUNK: AtomicUsize = AtomicUsize::new(0);
2746 static C_ABI_COMPRESS_PREFILTER_SET: AtomicBool = AtomicBool::new(false);
2747 static C_ABI_COMPRESS_PREPARAMS: AtomicUsize = AtomicUsize::new(0);
2748 static C_ABI_COMPRESS_TUNER_ID: AtomicI32 = AtomicI32::new(0);
2749 static C_ABI_COMPRESS_INSTR_CODEC: AtomicBool = AtomicBool::new(false);
2750 static C_ABI_COMPRESS_CODEC_PARAMS: AtomicUsize = AtomicUsize::new(0);
2751 static C_ABI_DECOMPRESS_INPUT_LEN: AtomicI32 = AtomicI32::new(0);
2752 static C_ABI_DECOMPRESS_OUTPUT_LEN: AtomicI32 = AtomicI32::new(0);
2753 static C_ABI_DECOMPRESS_META: AtomicU8 = AtomicU8::new(0);
2754 static C_ABI_DECOMPRESS_TYPESIZE: AtomicI32 = AtomicI32::new(0);
2755 static C_ABI_DECOMPRESS_INPUT_PTR: AtomicUsize = AtomicUsize::new(0);
2756 static C_ABI_DECOMPRESS_SCHUNK: AtomicUsize = AtomicUsize::new(0);
2757 static C_ABI_DECOMPRESS_CHUNK: AtomicUsize = AtomicUsize::new(0);
2758 static C_ABI_DECOMPRESS_POSTFILTER_SET: AtomicBool = AtomicBool::new(false);
2759 static C_ABI_DECOMPRESS_POSTPARAMS: AtomicUsize = AtomicUsize::new(0);
2760
2761 unsafe extern "C" fn c_abi_codec_encoder(
2762 input: *const u8,
2763 input_len: i32,
2764 output: *mut u8,
2765 output_len: i32,
2766 meta: u8,
2767 cparams: *mut Blosc2CParams,
2768 chunk: *const c_void,
2769 ) -> i32 {
2770 if input.is_null() || output.is_null() || cparams.is_null() {
2771 return -1;
2772 }
2773 let cparams = unsafe { &*cparams };
2774 C_ABI_COMPRESS_INPUT_LEN.store(input_len, Ordering::SeqCst);
2775 C_ABI_COMPRESS_OUTPUT_LEN.store(output_len, Ordering::SeqCst);
2776 C_ABI_COMPRESS_META.store(meta, Ordering::SeqCst);
2777 C_ABI_COMPRESS_INPUT_PTR.store(input as usize, Ordering::SeqCst);
2778 C_ABI_COMPRESS_CLEVEL.store(cparams.clevel, Ordering::SeqCst);
2779 C_ABI_COMPRESS_COMP_META.store(cparams.compcode_meta, Ordering::SeqCst);
2780 C_ABI_COMPRESS_USE_DICT.store(cparams.use_dict, Ordering::SeqCst);
2781 C_ABI_COMPRESS_TYPESIZE.store(cparams.typesize, Ordering::SeqCst);
2782 C_ABI_COMPRESS_BLOCKSIZE.store(cparams.blocksize, Ordering::SeqCst);
2783 C_ABI_COMPRESS_SPLITMODE.store(cparams.splitmode, Ordering::SeqCst);
2784 C_ABI_COMPRESS_FILTER_LAST.store(cparams.filters[BLOSC2_MAX_FILTERS - 1], Ordering::SeqCst);
2785 C_ABI_COMPRESS_SCHUNK.store(cparams.schunk as usize, Ordering::SeqCst);
2786 C_ABI_COMPRESS_CHUNK.store(chunk as usize, Ordering::SeqCst);
2787 C_ABI_COMPRESS_PREFILTER_SET.store(cparams.prefilter.is_some(), Ordering::SeqCst);
2788 C_ABI_COMPRESS_PREPARAMS.store(cparams.preparams as usize, Ordering::SeqCst);
2789 C_ABI_COMPRESS_TUNER_ID.store(cparams.tuner_id, Ordering::SeqCst);
2790 C_ABI_COMPRESS_INSTR_CODEC.store(cparams.instr_codec, Ordering::SeqCst);
2791 C_ABI_COMPRESS_CODEC_PARAMS.store(cparams.codec_params as usize, Ordering::SeqCst);
2792 if input_len < 0 || output_len < input_len {
2793 return 0;
2794 }
2795 unsafe {
2796 std::ptr::copy_nonoverlapping(input, output, input_len as usize);
2797 }
2798 input_len
2799 }
2800
2801 unsafe extern "C" fn c_abi_codec_decoder(
2802 input: *const u8,
2803 input_len: i32,
2804 output: *mut u8,
2805 output_len: i32,
2806 meta: u8,
2807 dparams: *mut Blosc2DParams,
2808 chunk: *const c_void,
2809 ) -> i32 {
2810 if input.is_null() || output.is_null() || dparams.is_null() {
2811 return -1;
2812 }
2813 let dparams = unsafe { &*dparams };
2814 C_ABI_DECOMPRESS_INPUT_LEN.store(input_len, Ordering::SeqCst);
2815 C_ABI_DECOMPRESS_OUTPUT_LEN.store(output_len, Ordering::SeqCst);
2816 C_ABI_DECOMPRESS_META.store(meta, Ordering::SeqCst);
2817 C_ABI_DECOMPRESS_INPUT_PTR.store(input as usize, Ordering::SeqCst);
2818 C_ABI_DECOMPRESS_TYPESIZE.store(dparams.typesize, Ordering::SeqCst);
2819 C_ABI_DECOMPRESS_SCHUNK.store(dparams.schunk as usize, Ordering::SeqCst);
2820 C_ABI_DECOMPRESS_CHUNK.store(chunk as usize, Ordering::SeqCst);
2821 C_ABI_DECOMPRESS_POSTFILTER_SET.store(dparams.postfilter.is_some(), Ordering::SeqCst);
2822 C_ABI_DECOMPRESS_POSTPARAMS.store(dparams.postparams as usize, Ordering::SeqCst);
2823 if input_len < 0 || output_len < input_len {
2824 return -1;
2825 }
2826 unsafe {
2827 std::ptr::copy_nonoverlapping(input, output, input_len as usize);
2828 }
2829 output_len
2830 }
2831
2832 #[test]
2833 fn blosc_clevel_to_zstd_matches_c_library_mapping() {
2834 let expected = [
2837 (0, -1),
2838 (1, 1),
2839 (2, 3),
2840 (3, 5),
2841 (4, 7),
2842 (5, 9),
2843 (6, 11),
2844 (7, 13),
2845 (8, 15),
2846 (9, 22),
2847 ];
2848 for (blosc, zstd) in expected {
2849 assert_eq!(
2850 blosc_clevel_to_zstd(blosc),
2851 zstd,
2852 "blosc {blosc} must map to zstd {zstd}"
2853 );
2854 }
2855 }
2856
2857 #[test]
2858 fn lz4_acceleration_matches_current_c_blosc2_fast_mode() {
2859 let expected = [
2862 (0, 1),
2863 (1, 1),
2864 (2, 1),
2865 (3, 1),
2866 (4, 1),
2867 (5, 1),
2868 (6, 1),
2869 (7, 1),
2870 (8, 1),
2871 (9, 1),
2872 ];
2873 for (clevel, accel) in expected {
2874 assert_eq!(lz4_acceleration(clevel), accel);
2875 }
2876 }
2877
2878 #[test]
2879 fn lz4_fast_paths_roundtrip_with_clevel_acceleration() {
2880 let data = b"abcdefghijklmnopabcdefghZZZZabcdefghijklmnopabcdefghijklmnop";
2881 let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
2882
2883 let mut low = vec![0; 256];
2884 let mut high = vec![0; 256];
2885 let low_size = lz4_compress(1, data, &mut low);
2886 let high_size = lz4_compress(9, data, &mut high);
2887 assert!(low_size > 0);
2888 assert!(high_size > 0);
2889 let mut restored = vec![0; data.len()];
2890 assert_eq!(
2891 lz4_decompress(&low[..low_size as usize], &mut restored),
2892 data.len() as i32
2893 );
2894 assert_eq!(restored, data);
2895 restored.fill(0);
2896 assert_eq!(
2897 lz4_decompress(&high[..high_size as usize], &mut restored),
2898 data.len() as i32
2899 );
2900 assert_eq!(restored, data);
2901
2902 low.fill(0);
2903 high.fill(0);
2904 let low_size = lz4_compress_with_dict(1, data, &mut low, dict);
2905 let high_size = lz4_compress_with_dict(9, data, &mut high, dict);
2906 assert!(low_size > 0);
2907 assert!(high_size > 0);
2908 restored.fill(0);
2909 assert_eq!(
2910 lz4_decompress_with_dict(&low[..low_size as usize], &mut restored, dict),
2911 data.len() as i32
2912 );
2913 assert_eq!(restored, data);
2914 restored.fill(0);
2915 assert_eq!(
2916 lz4_decompress_with_dict(&high[..high_size as usize], &mut restored, dict),
2917 data.len() as i32
2918 );
2919 assert_eq!(restored, data);
2920 }
2921
2922 #[test]
2923 fn lz4hc_size_guard_returns_c_2gb_limit_sentinel() {
2924 let c_lz4hc_limit = 2usize << 30;
2925 assert_eq!(lz4hc_2gb_limit_result(c_lz4hc_limit), None);
2926 assert_eq!(
2927 lz4hc_2gb_limit_result(c_lz4hc_limit + 1),
2928 Some(BLOSC2_ERROR_2GB_LIMIT)
2929 );
2930 }
2931
2932 #[test]
2933 fn zlib_decompress_returns_actual_size_for_oversized_output_like_c() {
2934 let data = b"zlib payload";
2935 let mut compressed = vec![0; 128];
2936 let cbytes = zlib_compress(data, &mut compressed, 5);
2937 assert!(cbytes > 0);
2938
2939 let mut decoded = vec![0xaa; data.len() + 8];
2940 assert_eq!(
2941 zlib_decompress(&compressed[..cbytes as usize], &mut decoded),
2942 data.len() as i32
2943 );
2944 assert_eq!(&decoded[..data.len()], data);
2945 assert_eq!(&decoded[data.len()..], &[0xaa; 8]);
2946 }
2947
2948 #[test]
2949 fn zstd_decompress_returns_actual_size_for_oversized_output_like_c() {
2950 let data = b"zstd payload";
2951 let mut compressed = vec![0; 128];
2952 let cbytes = zstd_compress(data, &mut compressed, 5);
2953 assert!(cbytes > 0);
2954
2955 let mut decoded = vec![0xaa; data.len() + 8];
2956 assert_eq!(
2957 zstd_decompress(&compressed[..cbytes as usize], &mut decoded),
2958 data.len() as i32
2959 );
2960 assert_eq!(&decoded[..data.len()], data);
2961 assert_eq!(&decoded[data.len()..], &[0xaa; 8]);
2962 }
2963
2964 #[test]
2965 fn zstd_dict_decompress_returns_actual_size_for_oversized_output_like_c() {
2966 let dict = b"payload dictionary payload dictionary";
2967 let data = b"dictionary payload";
2968 let mut compressed = vec![0; 128];
2969 let cbytes = zstd_compress_with_dict(data, &mut compressed, 5, dict);
2970 assert!(cbytes > 0);
2971
2972 let mut decoded = vec![0xaa; data.len() + 8];
2973 assert_eq!(
2974 zstd_decompress_with_dict(&compressed[..cbytes as usize], &mut decoded, dict),
2975 data.len() as i32
2976 );
2977 assert_eq!(&decoded[..data.len()], data);
2978 assert_eq!(&decoded[data.len()..], &[0xaa; 8]);
2979 }
2980
2981 #[test]
2982 fn known_global_codec_metadata_matches_c_registry() {
2983 for (code, _name) in [
2984 (BLOSC_CODEC_NDLZ, "ndlz"),
2985 (BLOSC_CODEC_ZFP_FIXED_ACCURACY, "zfp_acc"),
2986 (BLOSC_CODEC_ZFP_FIXED_PRECISION, "zfp_prec"),
2987 (BLOSC_CODEC_ZFP_FIXED_RATE, "zfp_rate"),
2988 (BLOSC_CODEC_OPENHTJ2K, "openhtj2k"),
2989 (BLOSC_CODEC_GROK, "grok"),
2990 (BLOSC_CODEC_OPENZL, "openzl"),
2991 ] {
2992 assert!(is_known_global_codec(code));
2993 }
2994 for (code, name) in [
2995 (BLOSC_CODEC_NDLZ, "ndlz"),
2996 (BLOSC_CODEC_ZFP_FIXED_ACCURACY, "zfp_acc"),
2997 (BLOSC_CODEC_ZFP_FIXED_PRECISION, "zfp_prec"),
2998 (BLOSC_CODEC_ZFP_FIXED_RATE, "zfp_rate"),
2999 (BLOSC_CODEC_OPENHTJ2K, "openhtj2k"),
3000 (BLOSC_CODEC_GROK, "grok"),
3001 (BLOSC_CODEC_OPENZL, "openzl"),
3002 ] {
3003 assert!(is_registered_codec(code));
3004 assert_eq!(registered_codec_name(code), Some(name));
3005 assert_eq!(registered_codec_code(name), Some(code));
3006 assert_eq!(registered_codec_version(code), Some(1));
3007 assert_eq!(registered_codec_name_by_complib(code), Some(name));
3008 assert_eq!(
3009 registered_codec_complib_info(name),
3010 Some((code, name, "unknown"))
3011 );
3012 }
3013 }
3014
3015 #[test]
3016 fn unimplemented_or_missing_plugin_codecs_return_c_support_error() {
3017 let mut compressed = vec![0u8; 128];
3018 let mut decompressed = vec![0u8; 128];
3019
3020 assert_eq!(
3021 compress_block(BLOSC_CODEC_OPENHTJ2K, 5, b"payload", &mut compressed),
3022 BLOSC2_ERROR_CODEC_SUPPORT
3023 );
3024 assert_eq!(
3025 decompress_block(BLOSC_CODEC_OPENHTJ2K, b"payload", &mut decompressed),
3026 BLOSC2_ERROR_CODEC_SUPPORT
3027 );
3028 assert_eq!(
3029 compress_block(250, 5, b"payload", &mut compressed),
3030 BLOSC2_ERROR_CODEC_SUPPORT
3031 );
3032 assert_eq!(
3033 decompress_block(250, b"payload", &mut decompressed),
3034 BLOSC2_ERROR_CODEC_SUPPORT
3035 );
3036 }
3037
3038 fn passthrough_codec_compress(_clevel: u8, _meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
3039 if dest.len() < src.len() {
3040 return 0;
3041 }
3042 dest[..src.len()].copy_from_slice(src);
3043 src.len() as i32
3044 }
3045
3046 fn passthrough_codec_decompress(_meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
3047 if dest.len() < src.len() {
3048 return 0;
3049 }
3050 dest[..src.len()].copy_from_slice(src);
3051 src.len() as i32
3052 }
3053
3054 fn short_codec_compress(_clevel: u8, _meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
3055 if src.is_empty() || dest.is_empty() {
3056 return 0;
3057 }
3058 dest[0] = src[0];
3059 1
3060 }
3061
3062 fn short_codec_decompress(_meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
3063 if src.is_empty() || dest.is_empty() {
3064 return 0;
3065 }
3066 dest[0] = src[0];
3067 1
3068 }
3069
3070 fn negative_codec_compress(_clevel: u8, _meta: u8, _src: &[u8], _dest: &mut [u8]) -> i32 {
3071 -1
3072 }
3073
3074 fn oversized_codec_compress(_clevel: u8, _meta: u8, _src: &[u8], dest: &mut [u8]) -> i32 {
3075 dest.len() as i32 + 1
3076 }
3077
3078 fn exact_codec_decompress(_meta: u8, src: &[u8], dest: &mut [u8]) -> i32 {
3079 for (index, byte) in dest.iter_mut().enumerate() {
3080 *byte = src.get(index).copied().unwrap_or(0);
3081 }
3082 dest.len() as i32
3083 }
3084
3085 fn negative_codec_decompress(_meta: u8, _src: &[u8], _dest: &mut [u8]) -> i32 {
3086 -1
3087 }
3088
3089 fn oversized_codec_decompress(_meta: u8, _src: &[u8], dest: &mut [u8]) -> i32 {
3090 dest.len() as i32 + 1
3091 }
3092
3093 fn context_passthrough_codec_compress(
3094 _ctx: &mut CodecCallbackContext<'_>,
3095 src: &[u8],
3096 dest: &mut [u8],
3097 ) -> i32 {
3098 if dest.len() < src.len() {
3099 return 0;
3100 }
3101 dest[..src.len()].copy_from_slice(src);
3102 src.len() as i32
3103 }
3104
3105 fn context_passthrough_codec_decompress(
3106 _ctx: &mut CodecCallbackContext<'_>,
3107 src: &[u8],
3108 dest: &mut [u8],
3109 ) -> i32 {
3110 if dest.len() < src.len() {
3111 return 0;
3112 }
3113 dest[..src.len()].copy_from_slice(src);
3114 src.len() as i32
3115 }
3116
3117 fn codec_test_context<'a>(b2nd_metalayer: Option<&'a [u8]>) -> CodecCallbackContext<'a> {
3118 CodecCallbackContext {
3119 compcode: BLOSC_CODEC_NDLZ,
3120 complib: None,
3121 meta: 4,
3122 clevel: 5,
3123 cparams: None,
3124 dparams: None,
3125 chunk: CodecChunkContext {
3126 schunk: 0,
3127 nchunk: -1,
3128 nblock: -1,
3129 chunk_source: 0,
3130 block_offset: 0,
3131 blocksize: 0,
3132 bsize: 0,
3133 },
3134 b2nd_metalayer,
3135 user_data: 0,
3136 }
3137 }
3138
3139 #[cfg(feature = "plugin-zfp")]
3140 fn zfp_test_chunk_context() -> CodecChunkContext {
3141 CodecChunkContext {
3142 schunk: 1,
3143 nchunk: 0,
3144 nblock: 0,
3145 chunk_source: 0,
3146 block_offset: 0,
3147 blocksize: 64,
3148 bsize: 64,
3149 }
3150 }
3151
3152 #[test]
3153 fn user_codec_callbacks_receive_existing_destination_bytes_like_c() {
3154 const COMPRESS_CODE: u8 = 240;
3155 const DECOMPRESS_CODE: u8 = 241;
3156
3157 register_codec(
3158 COMPRESS_CODE,
3159 short_codec_compress,
3160 passthrough_codec_decompress,
3161 )
3162 .unwrap();
3163 let mut compressed = vec![0xaa; 4];
3164 assert_eq!(
3165 compress_block(COMPRESS_CODE, 5, &[0x11, 0x22], &mut compressed),
3166 1
3167 );
3168 assert_eq!(compressed, vec![0x11, 0xaa, 0xaa, 0xaa]);
3169
3170 register_codec(
3171 DECOMPRESS_CODE,
3172 passthrough_codec_compress,
3173 short_codec_decompress,
3174 )
3175 .unwrap();
3176 let mut decompressed = vec![0xbb; 4];
3177 assert_eq!(
3178 decompress_block(DECOMPRESS_CODE, &[0x33, 0x44], &mut decompressed),
3179 BLOSC2_ERROR_DATA
3180 );
3181 assert_eq!(decompressed, vec![0x33, 0xbb, 0xbb, 0xbb]);
3182 }
3183
3184 #[test]
3185 fn user_codec_compress_callback_results_match_c() {
3186 const NEGATIVE_CODE: u8 = 242;
3187 const OVERSIZED_CODE: u8 = 243;
3188
3189 register_codec(
3190 NEGATIVE_CODE,
3191 negative_codec_compress,
3192 passthrough_codec_decompress,
3193 )
3194 .unwrap();
3195 let mut compressed = vec![0; 4];
3196 assert_eq!(
3197 compress_block(NEGATIVE_CODE, 5, b"payload", &mut compressed),
3198 BLOSC2_ERROR_DATA
3199 );
3200
3201 register_codec(
3202 OVERSIZED_CODE,
3203 oversized_codec_compress,
3204 passthrough_codec_decompress,
3205 )
3206 .unwrap();
3207 assert_eq!(
3208 compress_block(OVERSIZED_CODE, 5, b"payload", &mut compressed),
3209 BLOSC2_ERROR_WRITE_BUFFER
3210 );
3211 }
3212
3213 #[test]
3214 fn user_codec_decompress_callback_results_match_c() {
3215 const EXACT_CODE: u8 = 244;
3216 const NEGATIVE_CODE: u8 = 245;
3217 const OVERSIZED_CODE: u8 = 246;
3218
3219 register_codec(
3220 EXACT_CODE,
3221 passthrough_codec_compress,
3222 exact_codec_decompress,
3223 )
3224 .unwrap();
3225 let mut decompressed = vec![0; 4];
3226 assert_eq!(
3227 decompress_block(EXACT_CODE, b"ab", &mut decompressed),
3228 decompressed.len() as i32
3229 );
3230 assert_eq!(&decompressed, b"ab\0\0");
3231
3232 register_codec(
3233 NEGATIVE_CODE,
3234 passthrough_codec_compress,
3235 negative_codec_decompress,
3236 )
3237 .unwrap();
3238 assert_eq!(
3239 decompress_block(NEGATIVE_CODE, b"ab", &mut decompressed),
3240 BLOSC2_ERROR_DATA
3241 );
3242
3243 register_codec(
3244 OVERSIZED_CODE,
3245 passthrough_codec_compress,
3246 oversized_codec_decompress,
3247 )
3248 .unwrap();
3249 assert_eq!(
3250 decompress_block(OVERSIZED_CODE, b"ab", &mut decompressed),
3251 BLOSC2_ERROR_DATA
3252 );
3253 }
3254
3255 #[test]
3256 fn user_codec_decompress_rejects_i32_oversized_dest_len() {
3257 assert_eq!(
3261 normalize_user_decompress_result(0, i32::MAX as usize + 1),
3262 BLOSC2_ERROR_DATA
3263 );
3264 }
3265
3266 #[test]
3267 fn blosc2_codec_registration_rejects_builtin_and_global_ids() {
3268 let global_codec = Blosc2Codec {
3269 compcode: 39,
3270 compname: "public-global-id-rejected",
3271 complib: 39,
3272 version: 1,
3273 encoder: passthrough_codec_compress,
3274 decoder: passthrough_codec_decompress,
3275 };
3276 assert_eq!(
3277 blosc2_register_codec(&global_codec),
3278 BLOSC2_ERROR_CODEC_PARAM
3279 );
3280 assert_eq!(
3281 blosc2_register_codec_c(Some(&global_codec)),
3282 BLOSC2_ERROR_CODEC_PARAM
3283 );
3284
3285 let user_codec = Blosc2Codec {
3286 compcode: 247,
3287 compname: "public-user-id-accepted",
3288 complib: 247,
3289 version: 1,
3290 encoder: passthrough_codec_compress,
3291 decoder: passthrough_codec_decompress,
3292 };
3293 assert_eq!(blosc2_register_codec(&user_codec), BLOSC2_ERROR_SUCCESS);
3294
3295 assert_eq!(
3296 blosc2_register_codec_c(Some(&user_codec)),
3297 BLOSC2_ERROR_SUCCESS
3298 );
3299
3300 let builtin_codec = Blosc2Codec {
3301 compcode: BLOSC_LZ4,
3302 compname: "builtin-id-rejected",
3303 complib: BLOSC_LZ4,
3304 version: 1,
3305 encoder: passthrough_codec_compress,
3306 decoder: passthrough_codec_decompress,
3307 };
3308 assert_eq!(
3309 blosc2_register_codec(&builtin_codec),
3310 BLOSC2_ERROR_CODEC_PARAM
3311 );
3312 }
3313
3314 #[test]
3315 fn known_global_codec_registration_is_descriptor_idempotent_without_dispatch() {
3316 let mut compressed = vec![0; 32];
3317 #[cfg(feature = "plugin-zfp")]
3318 let unsupported_without_static_plugin = 0;
3319 #[cfg(not(feature = "plugin-zfp"))]
3320 let unsupported_without_static_plugin = BLOSC2_ERROR_CODEC_SUPPORT;
3321
3322 assert_eq!(
3323 compress_block(
3324 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3325 5,
3326 b"payload",
3327 &mut compressed
3328 ),
3329 unsupported_without_static_plugin
3330 );
3331 assert_eq!(
3332 registered_codec_name(BLOSC_CODEC_ZFP_FIXED_ACCURACY),
3333 Some("zfp_acc")
3334 );
3335
3336 assert_eq!(
3337 register_global_codec_with_metadata(
3338 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3339 "zfp_acc",
3340 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3341 1,
3342 passthrough_codec_compress,
3343 passthrough_codec_decompress,
3344 ),
3345 Ok(())
3346 );
3347 assert_eq!(
3348 register_private_codec_with_metadata(
3349 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3350 "zfp_acc",
3351 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3352 1,
3353 passthrough_codec_compress,
3354 passthrough_codec_decompress,
3355 ),
3356 Ok(())
3357 );
3358 assert_eq!(
3359 register_global_context_codec_with_metadata(
3360 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3361 "zfp_acc",
3362 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3363 1,
3364 context_passthrough_codec_compress,
3365 context_passthrough_codec_decompress,
3366 ),
3367 Ok(())
3368 );
3369
3370 assert_eq!(
3371 registered_codec_name(BLOSC_CODEC_ZFP_FIXED_ACCURACY),
3372 Some("zfp_acc")
3373 );
3374 assert_eq!(
3375 registered_codec_version(BLOSC_CODEC_ZFP_FIXED_ACCURACY),
3376 Some(1)
3377 );
3378 assert_eq!(
3379 registered_codec_complib_info("zfp_acc"),
3380 Some((BLOSC_CODEC_ZFP_FIXED_ACCURACY, "zfp_acc", "unknown"))
3381 );
3382 assert_eq!(
3383 compress_block(
3384 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3385 5,
3386 b"payload",
3387 &mut compressed
3388 ),
3389 unsupported_without_static_plugin
3390 );
3391 let mut decompressed = vec![0; 7];
3392 #[cfg(feature = "plugin-zfp")]
3393 let unsupported_decompress_without_static_plugin = 0;
3394 #[cfg(not(feature = "plugin-zfp"))]
3395 let unsupported_decompress_without_static_plugin = BLOSC2_ERROR_CODEC_SUPPORT;
3396 assert_eq!(
3397 decompress_block(
3398 BLOSC_CODEC_ZFP_FIXED_ACCURACY,
3399 b"payload",
3400 &mut decompressed
3401 ),
3402 unsupported_decompress_without_static_plugin
3403 );
3404 }
3405
3406 #[test]
3407 fn known_global_codec_registration_is_idempotent_for_builtin_dispatch_ids() {
3408 assert_eq!(
3409 register_global_codec_with_metadata(
3410 BLOSC_CODEC_NDLZ,
3411 "ndlz",
3412 BLOSC_CODEC_NDLZ,
3413 1,
3414 passthrough_codec_compress,
3415 passthrough_codec_decompress,
3416 ),
3417 Ok(())
3418 );
3419 }
3420
3421 #[test]
3422 fn blosc2_register_codec_abi_uses_raw_c_callback_shape() {
3423 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3424 const CODE: u8 = 170;
3425 let codec = Blosc2CodecAbi {
3426 compcode: CODE,
3427 compname: b"raw-c-abi-codec\0".as_ptr().cast(),
3428 complib: CODE,
3429 version: 3,
3430 encoder: Some(c_abi_codec_encoder),
3431 decoder: Some(c_abi_codec_decoder),
3432 };
3433 assert_eq!(
3434 blosc2_register_codec_abi(&codec as *const Blosc2CodecAbi),
3435 BLOSC2_ERROR_SUCCESS
3436 );
3437 assert_eq!(
3438 blosc2_register_codec_abi(std::ptr::null()),
3439 BLOSC2_ERROR_INVALID_PARAM
3440 );
3441 assert_eq!(registered_codec_name(CODE), Some("raw-c-abi-codec"));
3442 assert_eq!(registered_codec_version(CODE), Some(3));
3443
3444 C_ABI_COMPRESS_INPUT_LEN.store(0, Ordering::SeqCst);
3445 C_ABI_DECOMPRESS_INPUT_LEN.store(0, Ordering::SeqCst);
3446 let mut source_chunk = vec![0u8; 128];
3447 source_chunk.extend_from_slice(b"c-abi payload");
3448 let src = &source_chunk[128..];
3449 let filtered_block = src.to_vec();
3450 let mut encoded = vec![0; 64];
3451 let cparams = CodecCParamsContext {
3452 compcode: CODE,
3453 compcode_meta: 0x5a,
3454 clevel: 7,
3455 use_dict: 1,
3456 typesize: 4,
3457 blocksize: 0,
3458 splitmode: BLOSC_NEVER_SPLIT,
3459 filters: [BLOSC_NOFILTER; BLOSC2_MAX_FILTERS],
3460 filters_meta: [0; BLOSC2_MAX_FILTERS],
3461 nthreads: 2,
3462 nchunk: 33,
3463 user_data: 0xfeed,
3464 instr_codec: true,
3465 codec_params: 0xc0de,
3466 };
3467 let chunk = CodecChunkContext {
3468 schunk: 0x1234,
3469 nchunk: 33,
3470 nblock: 4,
3471 chunk_source: source_chunk.as_ptr() as usize,
3472 block_offset: 128,
3473 blocksize: 64,
3474 bsize: src.len(),
3475 };
3476 let cbytes = compress_block_with_context(
3477 CODE,
3478 7,
3479 0x5a,
3480 &filtered_block,
3481 &mut encoded,
3482 Some(CodecCallbackContext {
3483 compcode: CODE,
3484 complib: Some(CODE),
3485 meta: 0x5a,
3486 clevel: 7,
3487 cparams: Some(&cparams),
3488 dparams: None,
3489 chunk,
3490 b2nd_metalayer: None,
3491 user_data: 0xfeed,
3492 }),
3493 );
3494 assert_eq!(cbytes, src.len() as i32);
3495 assert_eq!(&encoded[..src.len()], src);
3496 assert_eq!(
3497 C_ABI_COMPRESS_INPUT_LEN.load(Ordering::SeqCst),
3498 src.len() as i32
3499 );
3500 assert_eq!(
3501 C_ABI_COMPRESS_INPUT_PTR.load(Ordering::SeqCst),
3502 filtered_block.as_ptr() as usize
3503 );
3504 assert_eq!(C_ABI_COMPRESS_OUTPUT_LEN.load(Ordering::SeqCst), 64);
3505 assert_eq!(C_ABI_COMPRESS_META.load(Ordering::SeqCst), 0x5a);
3506 assert_eq!(C_ABI_COMPRESS_CLEVEL.load(Ordering::SeqCst), 7);
3507 assert_eq!(C_ABI_COMPRESS_COMP_META.load(Ordering::SeqCst), 0x5a);
3508 assert_eq!(C_ABI_COMPRESS_USE_DICT.load(Ordering::SeqCst), 1);
3509 assert_eq!(C_ABI_COMPRESS_TYPESIZE.load(Ordering::SeqCst), 4);
3510 assert_eq!(C_ABI_COMPRESS_BLOCKSIZE.load(Ordering::SeqCst), 0);
3511 assert_eq!(
3512 C_ABI_COMPRESS_SPLITMODE.load(Ordering::SeqCst),
3513 BLOSC_NEVER_SPLIT
3514 );
3515 assert_eq!(
3516 C_ABI_COMPRESS_FILTER_LAST.load(Ordering::SeqCst),
3517 BLOSC_NOFILTER
3518 );
3519 assert_eq!(C_ABI_COMPRESS_SCHUNK.load(Ordering::SeqCst), 0x1234);
3520 assert_eq!(
3521 C_ABI_COMPRESS_CHUNK.load(Ordering::SeqCst),
3522 source_chunk.as_ptr() as usize
3523 );
3524 assert!(!C_ABI_COMPRESS_PREFILTER_SET.load(Ordering::SeqCst));
3525 assert_eq!(C_ABI_COMPRESS_PREPARAMS.load(Ordering::SeqCst), 0);
3526 assert_eq!(C_ABI_COMPRESS_TUNER_ID.load(Ordering::SeqCst), 0);
3527 assert!(C_ABI_COMPRESS_INSTR_CODEC.load(Ordering::SeqCst));
3528 assert_eq!(C_ABI_COMPRESS_CODEC_PARAMS.load(Ordering::SeqCst), 0xc0de);
3529
3530 let dparams = CodecDParamsContext {
3531 nthreads: 3,
3532 typesize: 4,
3533 nchunk: 34,
3534 user_data: 0xbeef,
3535 };
3536 let mut decoded = vec![0; src.len()];
3537 let mut compressed_chunk_bytes = vec![0u8; 128];
3538 compressed_chunk_bytes.extend_from_slice(&encoded[..cbytes as usize]);
3539 let compressed_block = encoded[..cbytes as usize].to_vec();
3540 assert_eq!(
3541 decompress_block_with_context(
3542 CODE,
3543 0x5a,
3544 &compressed_block,
3545 &mut decoded,
3546 Some(CodecCallbackContext {
3547 compcode: CODE,
3548 complib: Some(CODE),
3549 meta: 0x5a,
3550 clevel: 0,
3551 cparams: None,
3552 dparams: Some(&dparams),
3553 chunk: CodecChunkContext {
3554 nchunk: 34,
3555 chunk_source: compressed_chunk_bytes.as_ptr() as usize,
3556 ..chunk
3557 },
3558 b2nd_metalayer: None,
3559 user_data: 0xbeef,
3560 }),
3561 ),
3562 src.len() as i32
3563 );
3564 assert_eq!(decoded, src);
3565 assert_eq!(
3566 C_ABI_DECOMPRESS_INPUT_LEN.load(Ordering::SeqCst),
3567 src.len() as i32
3568 );
3569 assert_eq!(
3570 C_ABI_DECOMPRESS_INPUT_PTR.load(Ordering::SeqCst),
3571 compressed_block.as_ptr() as usize
3572 );
3573 assert_eq!(
3574 C_ABI_DECOMPRESS_OUTPUT_LEN.load(Ordering::SeqCst),
3575 src.len() as i32
3576 );
3577 assert_eq!(C_ABI_DECOMPRESS_META.load(Ordering::SeqCst), 0x5a);
3578 assert_eq!(C_ABI_DECOMPRESS_TYPESIZE.load(Ordering::SeqCst), 4);
3579 assert_eq!(C_ABI_DECOMPRESS_SCHUNK.load(Ordering::SeqCst), 0x1234);
3580 assert_eq!(
3581 C_ABI_DECOMPRESS_CHUNK.load(Ordering::SeqCst),
3582 compressed_chunk_bytes.as_ptr() as usize
3583 );
3584 assert!(!C_ABI_DECOMPRESS_POSTFILTER_SET.load(Ordering::SeqCst));
3585 assert_eq!(C_ABI_DECOMPRESS_POSTPARAMS.load(Ordering::SeqCst), 0);
3586 }
3587
3588 #[test]
3589 fn blosc2_register_codec_abi_fallback_params_match_c_defaults() {
3590 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3591 const CODE: u8 = 171;
3592 let codec = Blosc2CodecAbi {
3593 compcode: CODE,
3594 compname: b"raw-c-abi-defaults\0".as_ptr().cast(),
3595 complib: CODE,
3596 version: 1,
3597 encoder: Some(c_abi_codec_encoder),
3598 decoder: Some(c_abi_codec_decoder),
3599 };
3600 assert_eq!(
3601 blosc2_register_codec_abi(&codec as *const Blosc2CodecAbi),
3602 BLOSC2_ERROR_SUCCESS
3603 );
3604
3605 let src = b"default params";
3606 let mut encoded = vec![0; 64];
3607 C_ABI_COMPRESS_CHUNK.store(usize::MAX, Ordering::SeqCst);
3608 assert_eq!(
3609 compress_block_with_meta(CODE, 6, 0x23, src, &mut encoded),
3610 src.len() as i32
3611 );
3612 assert_eq!(
3613 C_ABI_COMPRESS_CHUNK.load(Ordering::SeqCst),
3614 src.as_ptr() as usize
3615 );
3616 assert_eq!(C_ABI_COMPRESS_META.load(Ordering::SeqCst), 0x23);
3617 assert_eq!(C_ABI_COMPRESS_CLEVEL.load(Ordering::SeqCst), 6);
3618 assert_eq!(C_ABI_COMPRESS_USE_DICT.load(Ordering::SeqCst), 0);
3619 assert_eq!(C_ABI_COMPRESS_TYPESIZE.load(Ordering::SeqCst), 8);
3620 assert_eq!(C_ABI_COMPRESS_BLOCKSIZE.load(Ordering::SeqCst), 0);
3621 assert_eq!(
3622 C_ABI_COMPRESS_SPLITMODE.load(Ordering::SeqCst),
3623 BLOSC_FORWARD_COMPAT_SPLIT
3624 );
3625 assert_eq!(
3626 C_ABI_COMPRESS_FILTER_LAST.load(Ordering::SeqCst),
3627 BLOSC_SHUFFLE
3628 );
3629 assert!(!C_ABI_COMPRESS_PREFILTER_SET.load(Ordering::SeqCst));
3630 assert_eq!(C_ABI_COMPRESS_PREPARAMS.load(Ordering::SeqCst), 0);
3631 assert_eq!(C_ABI_COMPRESS_TUNER_ID.load(Ordering::SeqCst), 0);
3632 assert!(!C_ABI_COMPRESS_INSTR_CODEC.load(Ordering::SeqCst));
3633 assert_eq!(C_ABI_COMPRESS_CODEC_PARAMS.load(Ordering::SeqCst), 0);
3634
3635 let mut decoded = vec![0; src.len()];
3636 C_ABI_DECOMPRESS_CHUNK.store(usize::MAX, Ordering::SeqCst);
3637 assert_eq!(
3638 decompress_block_with_meta(CODE, 0x23, &encoded[..src.len()], &mut decoded),
3639 src.len() as i32
3640 );
3641 assert_eq!(decoded, src);
3642 assert_eq!(
3643 C_ABI_DECOMPRESS_CHUNK.load(Ordering::SeqCst),
3644 encoded.as_ptr() as usize
3645 );
3646 assert_eq!(C_ABI_DECOMPRESS_TYPESIZE.load(Ordering::SeqCst), 8);
3647 assert!(!C_ABI_DECOMPRESS_POSTFILTER_SET.load(Ordering::SeqCst));
3648 assert_eq!(C_ABI_DECOMPRESS_POSTPARAMS.load(Ordering::SeqCst), 0);
3649 }
3650
3651 #[test]
3652 fn blosc2_register_codec_abi_null_chunk_source_fallback_is_null() {
3653 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3654 const CODE: u8 = 172;
3655 let codec = Blosc2CodecAbi {
3656 compcode: CODE,
3657 compname: b"raw-c-abi-null-chunk-fallback\0".as_ptr().cast(),
3658 complib: CODE,
3659 version: 1,
3660 encoder: Some(c_abi_codec_encoder),
3661 decoder: Some(c_abi_codec_decoder),
3662 };
3663 assert_eq!(
3664 blosc2_register_codec_abi(&codec as *const Blosc2CodecAbi),
3665 BLOSC2_ERROR_SUCCESS
3666 );
3667
3668 let src = b"null chunk";
3669 let mut encoded = vec![0; 32];
3670 C_ABI_COMPRESS_CHUNK.store(usize::MAX, Ordering::SeqCst);
3671 assert_eq!(
3672 compress_block_with_context(
3673 CODE,
3674 5,
3675 0,
3676 src,
3677 &mut encoded,
3678 Some(CodecCallbackContext {
3679 compcode: CODE,
3680 complib: Some(CODE),
3681 meta: 0,
3682 clevel: 5,
3683 cparams: None,
3684 dparams: None,
3685 chunk: CodecChunkContext {
3686 schunk: 0,
3687 nchunk: -1,
3688 nblock: 0,
3689 chunk_source: 0,
3690 block_offset: src.as_ptr() as usize,
3691 blocksize: src.len(),
3692 bsize: src.len(),
3693 },
3694 b2nd_metalayer: None,
3695 user_data: 0,
3696 }),
3697 ),
3698 src.len() as i32
3699 );
3700 assert_eq!(C_ABI_COMPRESS_CHUNK.load(Ordering::SeqCst), 0);
3701
3702 let mut decoded = vec![0; src.len()];
3703 C_ABI_DECOMPRESS_CHUNK.store(usize::MAX, Ordering::SeqCst);
3704 assert_eq!(
3705 decompress_block_with_context(
3706 CODE,
3707 0,
3708 &encoded[..src.len()],
3709 &mut decoded,
3710 Some(CodecCallbackContext {
3711 compcode: CODE,
3712 complib: Some(CODE),
3713 meta: 0,
3714 clevel: 0,
3715 cparams: None,
3716 dparams: None,
3717 chunk: CodecChunkContext {
3718 schunk: 0,
3719 nchunk: -1,
3720 nblock: 0,
3721 chunk_source: 0,
3722 block_offset: encoded.as_ptr() as usize,
3723 blocksize: src.len(),
3724 bsize: src.len(),
3725 },
3726 b2nd_metalayer: None,
3727 user_data: 0,
3728 }),
3729 ),
3730 src.len() as i32
3731 );
3732 assert_eq!(decoded, src);
3733 assert_eq!(C_ABI_DECOMPRESS_CHUNK.load(Ordering::SeqCst), 0);
3734 }
3735
3736 #[test]
3737 fn blosc2_register_codec_abi_null_callbacks_return_codec_support() {
3738 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3739 const NULL_ENCODER_CODE: u8 = 173;
3740 let null_encoder = Blosc2CodecAbi {
3741 compcode: NULL_ENCODER_CODE,
3742 compname: b"raw-c-abi-null-encoder\0".as_ptr().cast(),
3743 complib: NULL_ENCODER_CODE,
3744 version: 1,
3745 encoder: None,
3746 decoder: Some(c_abi_codec_decoder),
3747 };
3748 assert_eq!(
3749 blosc2_register_codec_abi(&null_encoder as *const Blosc2CodecAbi),
3750 BLOSC2_ERROR_SUCCESS
3751 );
3752 let mut encoded = vec![0; 32];
3753 assert_eq!(
3754 compress_block(NULL_ENCODER_CODE, 5, b"payload", &mut encoded),
3755 BLOSC2_ERROR_CODEC_SUPPORT
3756 );
3757
3758 const NULL_DECODER_CODE: u8 = 174;
3759 let null_decoder = Blosc2CodecAbi {
3760 compcode: NULL_DECODER_CODE,
3761 compname: b"raw-c-abi-null-decoder\0".as_ptr().cast(),
3762 complib: NULL_DECODER_CODE,
3763 version: 1,
3764 encoder: Some(c_abi_codec_encoder),
3765 decoder: None,
3766 };
3767 assert_eq!(
3768 blosc2_register_codec_abi(&null_decoder as *const Blosc2CodecAbi),
3769 BLOSC2_ERROR_SUCCESS
3770 );
3771 let mut decoded = vec![0; 7];
3772 assert_eq!(
3773 decompress_block(NULL_DECODER_CODE, b"payload", &mut decoded),
3774 BLOSC2_ERROR_CODEC_SUPPORT
3775 );
3776 }
3777
3778 #[test]
3779 fn blosc2_register_codec_abi_rejects_null_name_and_parses_empty_name() {
3780 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3781 const CODE: u8 = 175;
3782 let null_name = Blosc2CodecAbi {
3783 compcode: CODE,
3784 compname: std::ptr::null(),
3785 complib: CODE,
3786 version: 1,
3787 encoder: Some(c_abi_codec_encoder),
3788 decoder: Some(c_abi_codec_decoder),
3789 };
3790 assert_eq!(
3791 blosc2_register_codec_abi(&null_name as *const Blosc2CodecAbi),
3792 BLOSC2_ERROR_INVALID_PARAM
3793 );
3794 assert_eq!(blosc2_codec_abi_name(b"\0".as_ptr().cast()), Ok(""));
3795 }
3796
3797 #[test]
3798 fn blosc2_register_codec_abi_duplicate_id_matches_c_name_idempotence() {
3799 let _lock = C_ABI_TEST_LOCK.lock().unwrap();
3800 const CODE: u8 = 176;
3801 let codec = Blosc2CodecAbi {
3802 compcode: CODE,
3803 compname: b"raw-c-abi-duplicate-id\0".as_ptr().cast(),
3804 complib: CODE,
3805 version: 1,
3806 encoder: Some(c_abi_codec_encoder),
3807 decoder: Some(c_abi_codec_decoder),
3808 };
3809 assert_eq!(
3810 blosc2_register_codec_abi(&codec as *const Blosc2CodecAbi),
3811 BLOSC2_ERROR_SUCCESS
3812 );
3813 assert_eq!(
3814 blosc2_register_codec_abi(&codec as *const Blosc2CodecAbi),
3815 BLOSC2_ERROR_SUCCESS
3816 );
3817
3818 let same_callbacks_different_name = Blosc2CodecAbi {
3819 compname: b"raw-c-abi-other-name\0".as_ptr().cast(),
3820 ..codec
3821 };
3822 assert_eq!(
3823 blosc2_register_codec_abi(&same_callbacks_different_name as *const Blosc2CodecAbi),
3824 BLOSC2_ERROR_CODEC_PARAM
3825 );
3826 assert_eq!(registered_codec_name(CODE), Some("raw-c-abi-duplicate-id"));
3827 }
3828
3829 #[test]
3830 fn known_global_codec_registration_rejects_different_or_missing_names() {
3831 assert_eq!(
3832 register_global_codec(
3833 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3834 passthrough_codec_compress,
3835 passthrough_codec_decompress,
3836 ),
3837 Err("Global plugin codec ID already registered")
3838 );
3839 assert_eq!(
3840 register_global_codec_with_metadata(
3841 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3842 "not-zfp-prec",
3843 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3844 1,
3845 passthrough_codec_compress,
3846 passthrough_codec_decompress,
3847 ),
3848 Err("Global plugin codec ID already registered")
3849 );
3850 assert_eq!(
3851 register_private_codec_with_metadata(
3852 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3853 "not-zfp-prec",
3854 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3855 1,
3856 passthrough_codec_compress,
3857 passthrough_codec_decompress,
3858 ),
3859 Err("Private codec ID already registered")
3860 );
3861 assert_eq!(
3862 register_global_context_codec(
3863 BLOSC_CODEC_ZFP_FIXED_PRECISION,
3864 context_passthrough_codec_compress,
3865 context_passthrough_codec_decompress,
3866 ),
3867 Err("Global plugin codec ID already registered")
3868 );
3869 }
3870
3871 #[test]
3872 fn internal_global_codec_registration_accepts_global_ids_like_c_private_path() {
3873 const CODE: u8 = 40;
3874 assert_eq!(
3875 register_global_codec_with_metadata(
3876 CODE,
3877 "private-global-codec",
3878 CODE,
3879 1,
3880 passthrough_codec_compress,
3881 passthrough_codec_decompress,
3882 ),
3883 Ok(())
3884 );
3885 assert_eq!(registered_codec_name(CODE), Some("private-global-codec"));
3886 assert_eq!(
3887 registered_codec_complib_info("private-global-codec"),
3888 Some((CODE, "private-global-codec", "unknown"))
3889 );
3890 assert_eq!(
3891 register_global_codec_with_metadata(
3892 CODE,
3893 "private-global-codec",
3894 CODE,
3895 1,
3896 passthrough_codec_compress,
3897 passthrough_codec_decompress,
3898 ),
3899 Ok(())
3900 );
3901 }
3902
3903 #[test]
3904 fn unnamed_global_and_private_duplicate_registration_is_rejected() {
3905 const GLOBAL_CODE: u8 = 42;
3906 assert_eq!(
3907 register_global_codec(
3908 GLOBAL_CODE,
3909 passthrough_codec_compress,
3910 passthrough_codec_decompress,
3911 ),
3912 Ok(())
3913 );
3914 assert_eq!(
3915 register_global_codec(
3916 GLOBAL_CODE,
3917 passthrough_codec_compress,
3918 passthrough_codec_decompress,
3919 ),
3920 Err("Global plugin codec ID already registered")
3921 );
3922
3923 const PRIVATE_CODE: u8 = 43;
3924 assert_eq!(
3925 register_private_codec(
3926 PRIVATE_CODE,
3927 passthrough_codec_compress,
3928 passthrough_codec_decompress,
3929 ),
3930 Ok(())
3931 );
3932 assert_eq!(
3933 register_private_codec(
3934 PRIVATE_CODE,
3935 passthrough_codec_compress,
3936 passthrough_codec_decompress,
3937 ),
3938 Err("Private codec ID already registered")
3939 );
3940 }
3941
3942 #[test]
3943 fn private_codec_registration_accepts_user_ids_like_c_private_path() {
3944 const CODE: u8 = 251;
3945 assert_eq!(
3946 register_global_codec_with_metadata(
3947 CODE,
3948 "global-helper-user-id-rejected",
3949 CODE,
3950 1,
3951 passthrough_codec_compress,
3952 passthrough_codec_decompress,
3953 ),
3954 Err("Global plugin codec IDs must be in 32..=159")
3955 );
3956 assert_eq!(
3957 register_private_codec_with_metadata(
3958 CODE,
3959 "private-user-range-codec",
3960 CODE,
3961 2,
3962 passthrough_codec_compress,
3963 passthrough_codec_decompress,
3964 ),
3965 Ok(())
3966 );
3967 assert!(is_registered_codec(CODE));
3968 assert_eq!(
3969 registered_codec_name(CODE),
3970 Some("private-user-range-codec")
3971 );
3972 assert_eq!(
3973 registered_codec_code("private-user-range-codec"),
3974 Some(CODE)
3975 );
3976 assert_eq!(registered_codec_version(CODE), Some(2));
3977 assert_eq!(
3978 registered_codec_complib_info("private-user-range-codec"),
3979 Some((CODE, "private-user-range-codec", "unknown"))
3980 );
3981 assert_eq!(
3982 register_private_codec(
3983 BLOSC_LZ4,
3984 passthrough_codec_compress,
3985 passthrough_codec_decompress,
3986 ),
3987 Err("Private codec IDs must be >= 32")
3988 );
3989 }
3990
3991 #[test]
3992 fn registered_codec_complib_lookup_prefers_builtin_libraries_like_c() {
3993 const CODE: u8 = 41;
3994 register_global_codec_with_metadata(
3995 CODE,
3996 "lz4-backed-plugin",
3997 BLOSC_LZ4_LIB,
3998 1,
3999 passthrough_codec_compress,
4000 passthrough_codec_decompress,
4001 )
4002 .unwrap();
4003
4004 assert_eq!(
4005 registered_codec_name_by_complib(BLOSC_LZ4_LIB),
4006 Some(BLOSC_LZ4_LIBNAME)
4007 );
4008 assert_eq!(
4009 registered_codec_complib_info("lz4-backed-plugin"),
4010 Some((BLOSC_LZ4_LIB, BLOSC_LZ4_LIBNAME, "1.10.0"))
4011 );
4012 }
4013
4014 #[test]
4015 fn ndlz_compress_null_context_returns_c_fallback_sentinel() {
4016 let input: Vec<u8> = (1..=16).collect();
4017 let mut encoded = vec![0; 64];
4018 assert_eq!(
4019 compress_block_with_meta(BLOSC_CODEC_NDLZ, 5, 4, &input, &mut encoded),
4020 0
4021 );
4022 }
4023
4024 #[test]
4025 fn ndlz_compress_present_context_requires_valid_b2nd_metadata() {
4026 let input: Vec<u8> = (1..=16).collect();
4027 let mut encoded = vec![0; 64];
4028
4029 assert_eq!(
4030 compress_block_with_context(
4031 BLOSC_CODEC_NDLZ,
4032 5,
4033 4,
4034 &input,
4035 &mut encoded,
4036 Some(codec_test_context(None)),
4037 ),
4038 -1
4039 );
4040 assert_eq!(
4041 compress_block_with_context(
4042 BLOSC_CODEC_NDLZ,
4043 5,
4044 4,
4045 &input,
4046 &mut encoded,
4047 Some(codec_test_context(Some(b"invalid-b2nd"))),
4048 ),
4049 -1
4050 );
4051 }
4052
4053 #[cfg(feature = "plugin-zfp")]
4054 #[test]
4055 fn zfp_fixed_precision_meta_clamps_to_c_max_precision() {
4056 let config = zfp_config_for_mode(
4057 BLOSC_CODEC_ZFP_FIXED_PRECISION,
4058 u8::MAX,
4059 ZfpScalarType::Float,
4060 ZfpDimensionality::D4,
4061 );
4062
4063 assert_eq!(config.max_prec(), ZFP_MAX_PREC);
4064 }
4065
4066 #[cfg(feature = "plugin-zfp")]
4067 #[test]
4068 fn zfp_fixed_rate_roundtrip_uses_b2nd_blockshape_context() {
4069 let b2nd_meta = B2ndMeta::new(vec![4, 4], vec![4, 4], vec![4, 4], "<f4", 0)
4070 .unwrap()
4071 .serialize()
4072 .unwrap();
4073 let cparams = CodecCParamsContext {
4074 compcode: BLOSC_CODEC_ZFP_FIXED_RATE,
4075 compcode_meta: 25,
4076 clevel: 5,
4077 use_dict: 0,
4078 typesize: 4,
4079 blocksize: 64,
4080 splitmode: BLOSC_NEVER_SPLIT,
4081 filters: [BLOSC_NOFILTER; BLOSC2_MAX_FILTERS],
4082 filters_meta: [0; BLOSC2_MAX_FILTERS],
4083 nthreads: 1,
4084 nchunk: 0,
4085 user_data: 0,
4086 instr_codec: false,
4087 codec_params: 0,
4088 };
4089 let dparams = CodecDParamsContext {
4090 nthreads: 1,
4091 typesize: 4,
4092 nchunk: 0,
4093 user_data: 0,
4094 };
4095 let chunk = zfp_test_chunk_context();
4096 let input: Vec<u8> = (0..16)
4097 .flat_map(|i| ((i as f32) * 0.25 + 1.0).to_ne_bytes())
4098 .collect();
4099 let mut encoded = vec![0; 128];
4100 let cbytes = compress_block_with_context(
4101 BLOSC_CODEC_ZFP_FIXED_RATE,
4102 5,
4103 25,
4104 &input,
4105 &mut encoded,
4106 Some(CodecCallbackContext {
4107 compcode: BLOSC_CODEC_ZFP_FIXED_RATE,
4108 complib: None,
4109 meta: 25,
4110 clevel: 5,
4111 cparams: Some(&cparams),
4112 dparams: None,
4113 chunk,
4114 b2nd_metalayer: Some(&b2nd_meta),
4115 user_data: 0,
4116 }),
4117 );
4118 assert!(cbytes > 0 && cbytes < input.len() as i32);
4119
4120 let mut decoded = vec![0; input.len()];
4121 let dbytes = decompress_block_with_context(
4122 BLOSC_CODEC_ZFP_FIXED_RATE,
4123 25,
4124 &encoded[..cbytes as usize],
4125 &mut decoded,
4126 Some(CodecCallbackContext {
4127 compcode: BLOSC_CODEC_ZFP_FIXED_RATE,
4128 complib: None,
4129 meta: 25,
4130 clevel: 5,
4131 cparams: None,
4132 dparams: Some(&dparams),
4133 chunk,
4134 b2nd_metalayer: Some(&b2nd_meta),
4135 user_data: 0,
4136 }),
4137 );
4138 assert_eq!(dbytes, input.len() as i32);
4139 assert!(decoded.iter().any(|&byte| byte != 0));
4140 }
4141
4142 #[test]
4143 fn ndlz_literal_blocks_decompress_for_meta_4_and_8() {
4144 let mut src4 = vec![2];
4145 src4.extend_from_slice(&3i32.to_le_bytes());
4146 src4.extend_from_slice(&4i32.to_le_bytes());
4147 src4.push(0);
4148 src4.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
4149 let mut out4 = vec![0; 12];
4150 assert_eq!(
4151 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 4, &src4, &mut out4),
4152 12
4153 );
4154 assert_eq!(out4, (1..=12).collect::<Vec<_>>());
4155
4156 let mut src8 = vec![2];
4157 src8.extend_from_slice(&2i32.to_le_bytes());
4158 src8.extend_from_slice(&3i32.to_le_bytes());
4159 src8.push(0);
4160 src8.extend_from_slice(&[20, 21, 22, 23, 24, 25]);
4161 let mut out8 = vec![0; 6];
4162 assert_eq!(
4163 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 8, &src8, &mut out8),
4164 6
4165 );
4166 assert_eq!(out8, vec![20, 21, 22, 23, 24, 25]);
4167 }
4168
4169 #[test]
4170 fn ndlz_repeat_cells_and_invalid_meta_are_handled() {
4171 let mut src = vec![2];
4172 src.extend_from_slice(&4i32.to_le_bytes());
4173 src.extend_from_slice(&4i32.to_le_bytes());
4174 src.push(0x40);
4175 src.push(7);
4176 let mut out = vec![0; 16];
4177 assert_eq!(
4178 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 4, &src, &mut out),
4179 16
4180 );
4181 assert_eq!(out, vec![7; 16]);
4182
4183 assert_eq!(
4184 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 5, &src, &mut out),
4185 -1
4186 );
4187 }
4188
4189 #[test]
4190 fn ndlz_short_input_returns_c_zero_sentinel() {
4191 let mut out = vec![0; 16];
4192 assert_eq!(
4193 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 4, &[], &mut out),
4194 0
4195 );
4196 assert_eq!(
4197 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 8, &[2, 0, 0, 0, 0, 0, 0], &mut out),
4198 0
4199 );
4200 assert_eq!(
4201 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 4, &[2, 0, 0, 0, 0, 0, 0, 0], &mut out),
4202 -1
4203 );
4204 }
4205
4206 #[test]
4207 fn ndlz_literal_encoder_returns_c_fallback_for_oversized_blocks() {
4208 for (meta, blockshape, input) in [
4209 (4, [4, 4], (1..=16).collect::<Vec<_>>()),
4210 (8, [8, 8], (20..84).collect::<Vec<_>>()),
4211 ] {
4212 let mut encoded = vec![0; input.len() + 32];
4213 let cbytes = compress_ndlz_2d_block(meta, blockshape, &input, &mut encoded);
4214 assert_eq!(cbytes, 0);
4215 }
4216 }
4217
4218 #[test]
4219 fn ndlz_encoder_returns_zero_for_valid_fallback_cases() {
4220 let small_input = vec![1; 12];
4221 let mut encoded = vec![0; 64];
4222 assert_eq!(
4223 compress_ndlz_2d_block(4, [3, 4], &small_input, &mut encoded),
4224 0
4225 );
4226 let mut no_header_space = vec![0; 8];
4227 assert_eq!(
4228 compress_ndlz_2d_block(4, [3, 4], &small_input, &mut no_header_space),
4229 -1
4230 );
4231
4232 let input: Vec<u8> = (1..=16).collect();
4233 assert_eq!(
4234 compress_ndlz_2d_block(4, [4, 4], &input, &mut no_header_space),
4235 -1
4236 );
4237 let mut too_small_output = vec![0; 16];
4238 assert_eq!(
4239 compress_ndlz_2d_block(4, [4, 4], &input, &mut too_small_output),
4240 0
4241 );
4242 assert_eq!(compress_ndlz_2d_block(4, [4, 4], &input, &mut encoded), 0);
4243 let padded_input: Vec<u8> = (1..=20).collect();
4244 assert_eq!(
4245 compress_ndlz_2d_block(4, [4, 5], &padded_input, &mut encoded),
4246 0
4247 );
4248
4249 let mut encoded = vec![0; 64];
4250 assert_eq!(compress_ndlz_2d_block(5, [4, 4], &input, &mut encoded), -1);
4251 assert_eq!(
4252 compress_ndlz_2d_block(4, [4, 4], &input[..15], &mut encoded),
4253 -1
4254 );
4255 assert_eq!(compress_ndlz_2d_block(4, [-1, 4], &input, &mut encoded), -1);
4256 }
4257
4258 #[test]
4259 fn ndlz_encoder_applies_c_worst_case_cell_guard_before_each_cell() {
4260 let mut input = vec![7; 20];
4261 input[16..].copy_from_slice(&[1, 2, 3, 4]);
4262 let mut encoded = vec![0; 27];
4263
4264 assert_eq!(compress_ndlz_2d_block(4, [4, 5], &input, &mut encoded), 0);
4265 }
4266
4267 #[test]
4268 fn ndlz_encoder_uses_repeat_cell_token_for_full_constant_cells() {
4269 let input = vec![9; 16];
4270 let mut encoded = vec![0; 64];
4271 let cbytes = compress_ndlz_2d_block(4, [4, 4], &input, &mut encoded);
4272 assert_eq!(cbytes, 11);
4273 assert_eq!(&encoded[..11], &[2, 4, 0, 0, 0, 4, 0, 0, 0, 0x40, 9]);
4274
4275 let mut decoded = vec![0; input.len()];
4276 assert_eq!(
4277 decompress_block_with_meta(
4278 BLOSC_CODEC_NDLZ,
4279 4,
4280 &encoded[..cbytes as usize],
4281 &mut decoded,
4282 ),
4283 input.len() as i32
4284 );
4285 assert_eq!(decoded, input);
4286 }
4287
4288 #[test]
4289 fn ndlz_encoder_uses_full_cell_match_tokens_for_repeated_cells() {
4290 let first_cell: Vec<u8> = (1..=16).collect();
4291 let mut input = Vec::with_capacity(32);
4292 for row in 0..4 {
4293 input.extend_from_slice(&first_cell[row * 4..row * 4 + 4]);
4294 input.extend_from_slice(&first_cell[row * 4..row * 4 + 4]);
4295 }
4296
4297 let mut encoded = vec![0; 96];
4298 let cbytes = compress_ndlz_2d_block(4, [4, 8], &input, &mut encoded);
4299 assert_eq!(cbytes, 29);
4300 assert_eq!(encoded[9], 0);
4301 assert_eq!(encoded[26], 0xc0);
4302 assert_eq!(u16::from_le_bytes([encoded[27], encoded[28]]), 16);
4303
4304 let mut decoded = vec![0; input.len()];
4305 assert_eq!(
4306 decompress_block_with_meta(
4307 BLOSC_CODEC_NDLZ,
4308 4,
4309 &encoded[..cbytes as usize],
4310 &mut decoded,
4311 ),
4312 input.len() as i32
4313 );
4314 assert_eq!(decoded, input);
4315 }
4316
4317 #[test]
4318 fn ndlz_encoder_uses_full_cell_match_tokens_for_repeated_8x8_cells() {
4319 let first_cell: Vec<u8> = (0..64).map(|i| ((i * 3 + 1) % 251) as u8).collect();
4320 let mut input = Vec::with_capacity(128);
4321 for row in 0..8 {
4322 input.extend_from_slice(&first_cell[row * 8..row * 8 + 8]);
4323 input.extend_from_slice(&first_cell[row * 8..row * 8 + 8]);
4324 }
4325
4326 let mut encoded = vec![0; 192];
4327 let cbytes = compress_ndlz_2d_block(8, [8, 16], &input, &mut encoded);
4328 assert_eq!(cbytes, 77);
4329 assert_eq!(encoded[9], 0);
4330 assert_eq!(encoded[74], 0xc0);
4331 assert_eq!(u16::from_le_bytes([encoded[75], encoded[76]]), 64);
4332
4333 let mut decoded = vec![0; input.len()];
4334 assert_eq!(
4335 decompress_block_with_meta(
4336 BLOSC_CODEC_NDLZ,
4337 8,
4338 &encoded[..cbytes as usize],
4339 &mut decoded,
4340 ),
4341 input.len() as i32
4342 );
4343 assert_eq!(decoded, input);
4344 }
4345
4346 #[test]
4347 fn ndlz_encoder_uses_row_match_tokens_for_4x4_cells() {
4348 let first_cell: Vec<u8> = (0..16).map(|i| (i + 1) as u8).collect();
4349
4350 let mut triple_input = Vec::with_capacity(64);
4351 for row in 0..4 {
4352 triple_input.extend_from_slice(&first_cell[row * 4..row * 4 + 4]);
4353 let second_row = match row {
4354 0 => &first_cell[4..8],
4355 1 => &first_cell[8..12],
4356 2 => &[90, 91, 92, 93],
4357 _ => &first_cell[12..16],
4358 };
4359 triple_input.extend_from_slice(second_row);
4360 triple_input.extend_from_slice(&[7, 7, 7, 7]);
4361 triple_input.extend_from_slice(&[8, 8, 8, 8]);
4362 }
4363
4364 let mut encoded = vec![0; 96];
4365 let cbytes = compress_ndlz_2d_block(4, [4, 16], &triple_input, &mut encoded);
4366 assert_eq!(cbytes, 37);
4367 assert_eq!(encoded[26], (7 << 5) | (2 << 3));
4368 assert_eq!(u16::from_le_bytes([encoded[27], encoded[28]]), 12);
4369
4370 let mut decoded = vec![0; triple_input.len()];
4371 assert_eq!(
4372 decompress_block_with_meta(
4373 BLOSC_CODEC_NDLZ,
4374 4,
4375 &encoded[..cbytes as usize],
4376 &mut decoded,
4377 ),
4378 triple_input.len() as i32
4379 );
4380 assert_eq!(decoded, triple_input);
4381
4382 let mut pair_input = Vec::with_capacity(64);
4383 for row in 0..4 {
4384 pair_input.extend_from_slice(&first_cell[row * 4..row * 4 + 4]);
4385 let second_row = match row {
4386 0 => &first_cell[0..4],
4387 1 => &[50, 51, 52, 53],
4388 2 => &first_cell[4..8],
4389 _ => &[60, 61, 62, 63],
4390 };
4391 pair_input.extend_from_slice(second_row);
4392 pair_input.extend_from_slice(&[7, 7, 7, 7]);
4393 pair_input.extend_from_slice(&[8, 8, 8, 8]);
4394 }
4395
4396 let cbytes = compress_ndlz_2d_block(4, [4, 16], &pair_input, &mut encoded);
4397 assert_eq!(cbytes, 41);
4398 assert_eq!(encoded[26], (1 << 7) | (2 << 3));
4399 assert_eq!(u16::from_le_bytes([encoded[27], encoded[28]]), 16);
4400
4401 let mut decoded = vec![0; pair_input.len()];
4402 assert_eq!(
4403 decompress_block_with_meta(
4404 BLOSC_CODEC_NDLZ,
4405 4,
4406 &encoded[..cbytes as usize],
4407 &mut decoded,
4408 ),
4409 pair_input.len() as i32
4410 );
4411 assert_eq!(decoded, pair_input);
4412 }
4413
4414 #[test]
4415 fn ndlz_encoder_uses_two_row_pair_match_token_for_4x4_cells() {
4416 let a = [1, 2, 3, 4];
4417 let b = [5, 6, 7, 8];
4418 let c = [9, 10, 11, 12];
4419 let d = [13, 14, 15, 16];
4420 let x = [21, 22, 23, 24];
4421 let y = [25, 26, 27, 28];
4422 let u = [31, 32, 33, 34];
4423 let v = [35, 36, 37, 38];
4424
4425 let rows = [[a, u, a], [b, c, b], [x, d, c], [y, v, d]];
4426 let mut input = Vec::with_capacity(48);
4427 for row in rows {
4428 for cell_row in row {
4429 input.extend_from_slice(&cell_row);
4430 }
4431 }
4432
4433 let mut encoded = vec![0; 96];
4434 let cbytes = compress_ndlz_2d_block(4, [4, 12], &input, &mut encoded);
4435 assert_eq!(cbytes, 48);
4436 assert_eq!(encoded[43], 40);
4437 assert_eq!(u16::from_le_bytes([encoded[44], encoded[45]]), 33);
4438 assert_eq!(u16::from_le_bytes([encoded[46], encoded[47]]), 12);
4439
4440 let mut decoded = vec![0; input.len()];
4441 assert_eq!(
4442 decompress_block_with_meta(BLOSC_CODEC_NDLZ, 4, &encoded[..48], &mut decoded),
4443 input.len() as i32
4444 );
4445 assert_eq!(decoded, input);
4446 }
4447
4448 #[test]
4449 fn ndlz_encoder_uses_row_match_tokens_for_8x8_cells() {
4450 let first_cell: Vec<u8> = (0..64).map(|i| ((i * 7 + 11) % 251) as u8).collect();
4451
4452 let mut triple_input = Vec::with_capacity(128);
4453 for row in 0..8 {
4454 triple_input.extend_from_slice(&first_cell[row * 8..row * 8 + 8]);
4455 let second_row: Vec<u8> = match row {
4456 2 => first_cell[0..8].to_vec(),
4457 3 => first_cell[8..16].to_vec(),
4458 4 => first_cell[16..24].to_vec(),
4459 _ => (0..8).map(|col| (180 + row * 8 + col) as u8).collect(),
4460 };
4461 triple_input.extend_from_slice(&second_row);
4462 }
4463
4464 let mut encoded = vec![0; 192];
4465 let cbytes = compress_ndlz_2d_block(8, [8, 16], &triple_input, &mut encoded);
4466 assert_eq!(cbytes, 117);
4467 assert_eq!(encoded[74], (21 << 3) | 2);
4468 assert_eq!(u16::from_le_bytes([encoded[75], encoded[76]]), 64);
4469
4470 let mut decoded = vec![0; triple_input.len()];
4471 assert_eq!(
4472 decompress_block_with_meta(
4473 BLOSC_CODEC_NDLZ,
4474 8,
4475 &encoded[..cbytes as usize],
4476 &mut decoded,
4477 ),
4478 triple_input.len() as i32
4479 );
4480 assert_eq!(decoded, triple_input);
4481
4482 let mut pair_input = Vec::with_capacity(128);
4483 for row in 0..8 {
4484 pair_input.extend_from_slice(&first_cell[row * 8..row * 8 + 8]);
4485 let second_row: Vec<u8> = match row {
4486 3 => first_cell[0..8].to_vec(),
4487 4 => first_cell[8..16].to_vec(),
4488 _ => (0..8).map(|col| (90 + row * 8 + col) as u8).collect(),
4489 };
4490 pair_input.extend_from_slice(&second_row);
4491 }
4492
4493 let cbytes = compress_ndlz_2d_block(8, [8, 16], &pair_input, &mut encoded);
4494 assert_eq!(cbytes, 125);
4495 assert_eq!(encoded[74], (17 << 3) | 3);
4496 assert_eq!(u16::from_le_bytes([encoded[75], encoded[76]]), 64);
4497
4498 let mut decoded = vec![0; pair_input.len()];
4499 assert_eq!(
4500 decompress_block_with_meta(
4501 BLOSC_CODEC_NDLZ,
4502 8,
4503 &encoded[..cbytes as usize],
4504 &mut decoded,
4505 ),
4506 pair_input.len() as i32
4507 );
4508 assert_eq!(decoded, pair_input);
4509 }
4510
4511 #[test]
4512 fn zstd_at_higher_blosc_level_compresses_better() {
4513 let data: Vec<u8> = (0..16384u32).flat_map(|i| (i % 17).to_le_bytes()).collect();
4518 let mut buf1 = vec![0u8; data.len() + 256];
4519 let mut buf9 = vec![0u8; data.len() + 256];
4520
4521 let csize1 = zstd_compress(&data, &mut buf1, 1);
4522 let csize9 = zstd_compress(&data, &mut buf9, 9);
4523
4524 assert!(csize1 > 0 && csize9 > 0, "compression must not fail");
4525 assert!(
4526 csize9 <= csize1,
4527 "level 9 must compress at least as well as level 1 (got {csize9} vs {csize1})"
4528 );
4529 }
4530
4531 #[test]
4532 fn zlib_zstd_direct_decompress_failures_return_c_zero_sentinel() {
4533 let mut dest = vec![0u8; 128];
4534 assert_eq!(
4535 decompress_block(BLOSC_ZLIB, b"not-a-zlib-block", &mut dest),
4536 0
4537 );
4538 let src = b"zlib payload";
4539 let mut compressed = vec![0u8; 128];
4540 let cbytes = zlib_compress(src, &mut compressed, 5);
4541 assert!(cbytes > 0);
4542 compressed.truncate(cbytes as usize);
4543 let mut zlib_out = vec![0u8; src.len()];
4544 assert_eq!(
4545 zlib_decompress(&compressed, &mut zlib_out),
4546 src.len() as i32
4547 );
4548 compressed.extend_from_slice(b"trailing");
4549 assert_eq!(
4550 zlib_decompress(&compressed, &mut zlib_out),
4551 src.len() as i32
4552 );
4553 assert_eq!(
4554 decompress_block(BLOSC_ZSTD, b"not-a-zstd-block", &mut dest),
4555 0
4556 );
4557 assert_eq!(
4558 decompress_block_with_dict(BLOSC_ZSTD, b"not-a-zstd-block", &mut dest, b"dict"),
4559 0
4560 );
4561 }
4562
4563 #[test]
4564 fn zstd_dictionary_matches_c_fixed_cdict_level() {
4565 let dict: Vec<u8> = (0..8192u32)
4566 .flat_map(|i| format!("record-{i:04}-COMMON-PREFIX-COMMON-SUFFIX;").into_bytes())
4567 .collect();
4568 let data: Vec<u8> = (0..16384u32)
4569 .flat_map(|i| {
4570 format!("record-{:04}-COMMON-PREFIX-COMMON-SUFFIX;", i % 8192).into_bytes()
4571 })
4572 .collect();
4573 let mut buf1 = vec![0u8; data.len() + 1024];
4574 let mut buf9 = vec![0u8; data.len() + 1024];
4575
4576 let csize1 = zstd_compress_with_dict(&data, &mut buf1, 1, &dict);
4577 let csize9 = zstd_compress_with_dict(&data, &mut buf9, 9, &dict);
4578
4579 assert!(
4580 csize1 > 0 && csize9 > 0,
4581 "dictionary compression must not fail"
4582 );
4583 assert_eq!(
4584 csize9, csize1,
4585 "C-Blosc2 creates Zstd CDicts at level 1 regardless of Blosc clevel"
4586 );
4587 assert_eq!(&buf9[..csize9 as usize], &buf1[..csize1 as usize]);
4588
4589 let mut restored = vec![0; data.len()];
4590 let dsize = zstd_decompress_with_dict(&buf9[..csize9 as usize], &mut restored, &dict);
4591 assert_eq!(dsize as usize, data.len());
4592 assert_eq!(restored, data);
4593 }
4594
4595 #[test]
4596 fn lz4hc_roundtrips_via_lz4_decoder() {
4597 let data: Vec<u8> = (0..8192u32).flat_map(|i| (i % 64).to_le_bytes()).collect();
4598 let mut compressed = vec![0; data.len() + 1024];
4599
4600 let csize = compress_block(BLOSC_LZ4HC, 9, &data, &mut compressed);
4601 assert!(csize > 0);
4602
4603 let mut decompressed = vec![0; data.len()];
4604 let dsize = decompress_block(
4605 BLOSC_LZ4HC,
4606 &compressed[..csize as usize],
4607 &mut decompressed,
4608 );
4609
4610 assert_eq!(dsize as usize, data.len());
4611 assert_eq!(decompressed, data);
4612
4613 let mut oversized = vec![0; data.len() + 1];
4614 assert_eq!(
4615 decompress_block(BLOSC_LZ4HC, &compressed[..csize as usize], &mut oversized),
4616 0
4617 );
4618 assert_eq!(
4619 decompress_block(BLOSC_LZ4HC, b"bad-lz4-block", &mut decompressed),
4620 0
4621 );
4622 }
4623
4624 #[test]
4625 fn lz4_dictionary_paths_roundtrip() {
4626 let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
4627 let data = b"abcdefghijklmnopabcdefghZZZZabcdefghijklmnop";
4628 let mut compressed = vec![0; 256];
4629 let mut decompressed = vec![0; data.len()];
4630
4631 let csize = lz4_compress_with_dict(5, data, &mut compressed, dict);
4632 assert!(csize > 0);
4633
4634 let dsize =
4635 lz4_decompress_with_dict(&compressed[..csize as usize], &mut decompressed, dict);
4636 assert_eq!(dsize as usize, data.len());
4637 assert_eq!(decompressed, data);
4638
4639 let mut oversized = vec![0; data.len() + 1];
4640 assert_eq!(
4641 lz4_decompress_with_dict(&compressed[..csize as usize], &mut oversized, dict),
4642 0
4643 );
4644 assert_eq!(
4645 lz4_decompress_with_dict(b"bad-lz4-block", &mut decompressed, dict),
4646 0
4647 );
4648 }
4649
4650 #[test]
4651 fn lz4hc_dictionary_paths_roundtrip() {
4652 let dict = b"abcdefghijklmnop0123456789abcdefghijklmnop0123456789";
4653 let data = b"abcdefghijklmnopabcdefghZZZZabcdefghijklmnop";
4654 let mut compressed = vec![0; 256];
4655 let mut decompressed = vec![0; data.len()];
4656
4657 let csize = lz4hc_compress_with_dict(9, data, &mut compressed, dict);
4658 assert!(csize > 0);
4659
4660 let dsize =
4661 lz4_decompress_with_dict(&compressed[..csize as usize], &mut decompressed, dict);
4662 assert_eq!(dsize as usize, data.len());
4663 assert_eq!(decompressed, data);
4664 }
4665}