blosc2/
lib.rs

1//! Blosc2 Rust bindings.
2
3use blosc2_sys as ffi;
4use parking_lot::{Mutex, RwLock};
5use std::ffi::{c_void, CStr, CString};
6use std::sync::{Arc, OnceLock};
7use std::{io, mem};
8
9pub const BLOSC2_VERSION_DATE: &'static str =
10    unsafe { std::str::from_utf8_unchecked(blosc2_sys::BLOSC2_VERSION_DATE) };
11pub const BLOSC2_VERSION_STRING: &'static str =
12    unsafe { std::str::from_utf8_unchecked(blosc2_sys::BLOSC2_VERSION_STRING) };
13pub use blosc2_sys::{
14    BLOSC2_MAX_DIM, BLOSC2_VERSION_MAJOR, BLOSC2_VERSION_MINOR, BLOSC2_VERSION_RELEASE,
15};
16
17const BLOSC2_GUARD: OnceLock<Arc<Blosc2Guard>> = OnceLock::new();
18const BLOSC2_INIT_FLAG: OnceLock<Arc<Mutex<bool>>> = OnceLock::new();
19
20/// Result type used in this library
21pub type Result<T> = std::result::Result<T, Error>;
22
23#[derive(Debug)]
24pub enum Error {
25    /// A Blosc2 library specific error. Often mapped to the
26    /// raw error return code
27    Blosc2(Blosc2Error),
28    /// Any other error, converted into a string
29    Other(String),
30}
31impl From<Blosc2Error> for Error {
32    fn from(err: Blosc2Error) -> Self {
33        Error::Blosc2(err)
34    }
35}
36impl From<String> for Error {
37    fn from(err: String) -> Self {
38        Error::Other(err)
39    }
40}
41impl<'a> From<&'a str> for Error {
42    fn from(err: &'a str) -> Self {
43        Error::Other(err.to_string())
44    }
45}
46
47impl From<Error> for io::Error {
48    fn from(err: Error) -> io::Error {
49        io::Error::new(io::ErrorKind::Other, err.to_string())
50    }
51}
52impl std::error::Error for Error {}
53
54impl std::fmt::Display for Error {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{:?}", self)
57    }
58}
59
60/// Possible errors arising from Blosc2 library
61#[derive(Debug)]
62#[repr(i32)]
63pub enum Blosc2Error {
64    /// Generic failure
65    Failure = ffi::BLOSC2_ERROR_FAILURE,
66    /// Bad stream
67    Stream = ffi::BLOSC2_ERROR_STREAM,
68    /// Invalid data
69    Data = ffi::BLOSC2_ERROR_DATA,
70    /// Memory alloc/realloc failure
71    MemoryAlloc = ffi::BLOSC2_ERROR_MEMORY_ALLOC,
72    /// Not enough space to read
73    ReadBuffer = ffi::BLOSC2_ERROR_READ_BUFFER,
74    /// Not enough space to write
75    WriteBuffer = ffi::BLOSC2_ERROR_WRITE_BUFFER,
76    /// Codec not supported
77    CodecSupport = ffi::BLOSC2_ERROR_CODEC_SUPPORT,
78    /// Invalid parameter supplied to codec
79    CodecParam = ffi::BLOSC2_ERROR_CODEC_PARAM,
80    /// Codec dictionary error
81    CodecDict = ffi::BLOSC2_ERROR_CODEC_DICT,
82    /// Version not supported
83    VersionSupport = ffi::BLOSC2_ERROR_VERSION_SUPPORT,
84    /// Invalid value in header
85    InvalidHeader = ffi::BLOSC2_ERROR_INVALID_HEADER,
86    /// Invalid parameter supplied to function
87    InvalidParam = ffi::BLOSC2_ERROR_INVALID_PARAM,
88    /// File read failure
89    FileRead = ffi::BLOSC2_ERROR_FILE_READ,
90    /// File write failure
91    FileWrite = ffi::BLOSC2_ERROR_FILE_WRITE,
92    /// File open failure
93    FileOpen = ffi::BLOSC2_ERROR_FILE_OPEN,
94    /// Not found
95    NotFound = ffi::BLOSC2_ERROR_NOT_FOUND,
96    /// Bad run length encoding
97    RunLength = ffi::BLOSC2_ERROR_RUN_LENGTH,
98    /// Filter pipeline error
99    FilterPipeline = ffi::BLOSC2_ERROR_FILTER_PIPELINE,
100    /// Chunk insert failure
101    ChunkInsert = ffi::BLOSC2_ERROR_CHUNK_INSERT,
102    /// Chunk append failure
103    ChunkAppend = ffi::BLOSC2_ERROR_CHUNK_APPEND,
104    /// Chunk update failure
105    ChunkUpdate = ffi::BLOSC2_ERROR_CHUNK_UPDATE,
106    /// Sizes larger than 2gb not supported
107    TwoGBLimit = ffi::BLOSC2_ERROR_2GB_LIMIT,
108    /// Super-chunk copy failure
109    SchunkCopy = ffi::BLOSC2_ERROR_SCHUNK_COPY,
110    /// Wrong type for frame
111    FrameType = ffi::BLOSC2_ERROR_FRAME_TYPE,
112    /// File truncate failure
113    FileTruncate = ffi::BLOSC2_ERROR_FILE_TRUNCATE,
114    /// Thread or thread context creation failure
115    ThreadCreate = ffi::BLOSC2_ERROR_THREAD_CREATE,
116    /// Postfilter failure
117    PostFilter = ffi::BLOSC2_ERROR_POSTFILTER,
118    /// Special frame failure
119    FrameSpecial = ffi::BLOSC2_ERROR_FRAME_SPECIAL,
120    /// Special super-chunk failure
121    SchunkSpecial = ffi::BLOSC2_ERROR_SCHUNK_SPECIAL,
122    /// IO plugin error
123    PluginIO = ffi::BLOSC2_ERROR_PLUGIN_IO,
124    /// Remove file failure
125    FileRemove = ffi::BLOSC2_ERROR_FILE_REMOVE,
126    /// Pointer is null
127    NullPointer = ffi::BLOSC2_ERROR_NULL_POINTER,
128    /// Invalid index
129    InvalidIndex = ffi::BLOSC2_ERROR_INVALID_INDEX,
130    /// Metalayer has not been found
131    MetalayerNotFound = ffi::BLOSC2_ERROR_METALAYER_NOT_FOUND,
132}
133
134impl std::fmt::Display for Blosc2Error {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "{:?}", self)
137    }
138}
139
140impl From<i32> for Blosc2Error {
141    fn from(code: i32) -> Self {
142        match code {
143            ffi::BLOSC2_ERROR_FAILURE => Blosc2Error::Failure,
144            ffi::BLOSC2_ERROR_STREAM => Blosc2Error::Stream,
145            ffi::BLOSC2_ERROR_DATA => Blosc2Error::Data,
146            ffi::BLOSC2_ERROR_MEMORY_ALLOC => Blosc2Error::MemoryAlloc,
147            ffi::BLOSC2_ERROR_READ_BUFFER => Blosc2Error::ReadBuffer,
148            ffi::BLOSC2_ERROR_WRITE_BUFFER => Blosc2Error::WriteBuffer,
149            ffi::BLOSC2_ERROR_CODEC_SUPPORT => Blosc2Error::CodecSupport,
150            ffi::BLOSC2_ERROR_CODEC_PARAM => Blosc2Error::CodecParam,
151            ffi::BLOSC2_ERROR_CODEC_DICT => Blosc2Error::CodecDict,
152            ffi::BLOSC2_ERROR_VERSION_SUPPORT => Blosc2Error::VersionSupport,
153            ffi::BLOSC2_ERROR_INVALID_HEADER => Blosc2Error::InvalidHeader,
154            ffi::BLOSC2_ERROR_INVALID_PARAM => Blosc2Error::InvalidParam,
155            ffi::BLOSC2_ERROR_FILE_READ => Blosc2Error::FileRead,
156            ffi::BLOSC2_ERROR_FILE_WRITE => Blosc2Error::FileWrite,
157            ffi::BLOSC2_ERROR_FILE_OPEN => Blosc2Error::FileOpen,
158            ffi::BLOSC2_ERROR_NOT_FOUND => Blosc2Error::NotFound,
159            ffi::BLOSC2_ERROR_RUN_LENGTH => Blosc2Error::RunLength,
160            ffi::BLOSC2_ERROR_FILTER_PIPELINE => Blosc2Error::FilterPipeline,
161            ffi::BLOSC2_ERROR_CHUNK_INSERT => Blosc2Error::ChunkInsert,
162            ffi::BLOSC2_ERROR_CHUNK_APPEND => Blosc2Error::ChunkAppend,
163            ffi::BLOSC2_ERROR_CHUNK_UPDATE => Blosc2Error::ChunkUpdate,
164            ffi::BLOSC2_ERROR_2GB_LIMIT => Blosc2Error::TwoGBLimit,
165            ffi::BLOSC2_ERROR_SCHUNK_COPY => Blosc2Error::SchunkCopy,
166            ffi::BLOSC2_ERROR_FRAME_TYPE => Blosc2Error::FrameType,
167            ffi::BLOSC2_ERROR_FILE_TRUNCATE => Blosc2Error::FileTruncate,
168            ffi::BLOSC2_ERROR_THREAD_CREATE => Blosc2Error::ThreadCreate,
169            ffi::BLOSC2_ERROR_POSTFILTER => Blosc2Error::PostFilter,
170            ffi::BLOSC2_ERROR_FRAME_SPECIAL => Blosc2Error::FrameSpecial,
171            ffi::BLOSC2_ERROR_SCHUNK_SPECIAL => Blosc2Error::SchunkSpecial,
172            ffi::BLOSC2_ERROR_PLUGIN_IO => Blosc2Error::PluginIO,
173            ffi::BLOSC2_ERROR_FILE_REMOVE => Blosc2Error::FileRemove,
174            ffi::BLOSC2_ERROR_NULL_POINTER => Blosc2Error::NullPointer,
175            ffi::BLOSC2_ERROR_INVALID_INDEX => Blosc2Error::InvalidIndex,
176            ffi::BLOSC2_ERROR_METALAYER_NOT_FOUND => Blosc2Error::MetalayerNotFound,
177            _ => panic!("Error code {} not matched in existing Error codes", code),
178        }
179    }
180}
181
182/// Default buffer size for intermediate de/compression results when required
183pub const BUFSIZE: usize = 8196_usize;
184
185/// Possible Filters
186#[derive(Copy, Clone)]
187pub enum Filter {
188    NoFilter = ffi::BLOSC_NOFILTER as _,
189    Shuffle = ffi::BLOSC_SHUFFLE as _,
190    BitShuffle = ffi::BLOSC_BITSHUFFLE as _,
191    Delta = ffi::BLOSC_DELTA as _,
192    TruncPrec = ffi::BLOSC_TRUNC_PREC as _,
193    LastFilter = ffi::BLOSC_LAST_FILTER as _,
194    LastRegisteredFilter = ffi::BLOSC_LAST_REGISTERED_FILTER as _,
195}
196
197impl Default for Filter {
198    fn default() -> Self {
199        Filter::Shuffle
200    }
201}
202
203impl ToString for Filter {
204    fn to_string(&self) -> String {
205        match self {
206            Self::NoFilter => "nofilter",
207            Self::Shuffle => "shuffle",
208            Self::BitShuffle => "bitshuffle",
209            Self::Delta => "delta",
210            Self::TruncPrec => "truncprec",
211            Self::LastFilter => "lastfilter",
212            Self::LastRegisteredFilter => "lastregisteredfilter",
213        }
214        .to_string()
215    }
216}
217
218impl<'a> TryFrom<&'a str> for Filter {
219    type Error = Error;
220
221    fn try_from(val: &'a str) -> Result<Self> {
222        match val.to_lowercase().as_str() {
223            "nofilter" => Ok(Filter::NoFilter),
224            "shuffle" => Ok(Filter::Shuffle),
225            "bitshuffle" => Ok(Filter::BitShuffle),
226            "delta" => Ok(Filter::Delta),
227            "trunctprec" => Ok(Filter::TruncPrec),
228            "lastfilter" => Ok(Filter::LastFilter),
229            "lastregisteredfilter" => Ok(Filter::LastRegisteredFilter),
230            _ => Err(Error::from(format!("No matching filter for '{}'", val))),
231        }
232    }
233}
234
235/// Possible compression codecs
236#[derive(Debug, Copy, Clone, PartialEq, Eq)]
237pub enum Codec {
238    BloscLz = ffi::BLOSC_BLOSCLZ as _,
239    LZ4 = ffi::BLOSC_LZ4 as _,
240    LZ4HC = ffi::BLOSC_LZ4HC as _,
241    ZLIB = ffi::BLOSC_ZLIB as _,
242    ZSTD = ffi::BLOSC_ZSTD as _,
243    LastCodec = ffi::BLOSC_LAST_CODEC as _,
244    LastRegisteredCodec = ffi::BLOSC_LAST_REGISTERED_CODEC as _,
245}
246
247impl Codec {
248    #[allow(dead_code)]
249    fn to_name(&self) -> Result<String> {
250        (*self).try_into()
251    }
252    fn to_name_cstring(&self) -> Result<CString> {
253        let mut compname = std::ptr::null();
254        let rc = unsafe { ffi::blosc2_compcode_to_compname(*self as _, &mut compname) };
255        if rc == -1 {
256            return Err(Error::Other(format!("Unsupported Codec {:?}", self)));
257        }
258        unsafe { Ok(CStr::from_ptr(compname as _).to_owned()) }
259    }
260}
261
262impl TryInto<String> for Codec {
263    type Error = Error;
264    fn try_into(self) -> Result<String> {
265        self.clone()
266            .to_name_cstring()?
267            .into_string()
268            .map_err(|e| Error::Other(e.to_string()))
269    }
270}
271impl<'a> TryFrom<&'a str> for Codec {
272    type Error = Error;
273
274    fn try_from(value: &'a str) -> Result<Self> {
275        let compname = CString::new(value).map_err(|e| Error::Other(e.to_string()))?;
276        let compcode = unsafe { ffi::blosc2_compname_to_compcode(compname.as_ptr()) };
277        if compcode == -1 {
278            return Err(Error::from(format!("Compcode {} not recognized", value)));
279        }
280        Codec::try_from(compcode)
281    }
282}
283impl TryFrom<i32> for Codec {
284    type Error = Error;
285
286    fn try_from(compcode: i32) -> Result<Self> {
287        match compcode as _ {
288            ffi::BLOSC_BLOSCLZ => Ok(Codec::BloscLz),
289            ffi::BLOSC_LZ4 => Ok(Codec::LZ4),
290            ffi::BLOSC_LZ4HC => Ok(Codec::LZ4HC),
291            ffi::BLOSC_ZLIB => Ok(Codec::ZLIB),
292            ffi::BLOSC_ZSTD => Ok(Codec::ZSTD),
293            ffi::BLOSC_LAST_CODEC => Ok(Codec::LastCodec),
294            ffi::BLOSC_LAST_REGISTERED_CODEC => Ok(Codec::LastRegisteredCodec),
295            _ => Err(format!("Not match for compcode {}", compcode).into()),
296        }
297    }
298}
299
300impl Default for Codec {
301    fn default() -> Self {
302        Codec::BloscLz
303    }
304}
305
306/// Possible CLevel settings
307#[repr(u8)]
308pub enum CLevel {
309    Zero = 0,
310    One = 1,
311    Two = 2,
312    Three = 3,
313    Four = 4,
314    Five = 5,
315    Six = 6,
316    Seven = 7,
317    Eight = 8,
318    Nine = 9,
319}
320
321impl Default for CLevel {
322    fn default() -> Self {
323        CLevel::Nine // Ref blosc2 python default
324    }
325}
326
327impl TryFrom<usize> for CLevel {
328    type Error = Error;
329
330    fn try_from(val: usize) -> Result<Self> {
331        match val {
332            0 => Ok(CLevel::Zero),
333            1 => Ok(CLevel::One),
334            2 => Ok(CLevel::Two),
335            3 => Ok(CLevel::Three),
336            4 => Ok(CLevel::Four),
337            5 => Ok(CLevel::Five),
338            6 => Ok(CLevel::Six),
339            7 => Ok(CLevel::Seven),
340            8 => Ok(CLevel::Eight),
341            9 => Ok(CLevel::Nine),
342            _ => Err(Error::from(format!(
343                "Compression level must be 0-9, got {}",
344                val
345            ))),
346        }
347    }
348}
349
350pub mod schunk {
351    //! `blosc2_schunk`,`blosc2_storage`, and `Chunk` APIs
352
353    use std::ffi::CStr;
354    use std::io;
355    use std::path::{Path, PathBuf};
356
357    use super::*;
358
359    /// Wrapper to [blosc2_storage]
360    ///
361    /// [blosc2_storage]: blosc2_sys::blosc2_storage
362    ///
363    /// Example
364    /// -------
365    /// ```
366    /// use blosc2::schunk::Storage;
367    ///
368    /// let storage = Storage::default().set_urlpath("/some/path.blosc2");
369    /// ```
370    pub struct Storage {
371        inner: ffi::blosc2_storage,
372        cparams: CParams,
373        dparams: DParams,
374    }
375
376    impl Default for Storage {
377        fn default() -> Self {
378            let storage = unsafe { ffi::blosc2_get_blosc2_storage_defaults() };
379            Storage {
380                inner: storage,
381                cparams: CParams::default(),
382                dparams: DParams::default(),
383            }
384        }
385    }
386
387    impl Storage {
388        /// Set url/file path to specify a file-backed `schunk`.
389        /// if not set, defaults to an in-memory `schunk`
390        pub fn set_urlpath<S: AsRef<Path>>(mut self, urlpath: S) -> Result<Self> {
391            self.inner.urlpath = CString::new(urlpath.as_ref().to_string_lossy().to_string())
392                .map_err(|e| Error::Other(e.to_string()))?
393                .into_raw();
394            Ok(self)
395        }
396        /// Reference to the urlpath (if any)
397        ///
398        /// Example
399        /// -------
400        /// ```
401        /// use blosc2::schunk::Storage;
402        ///
403        /// let storage = Storage::default().set_urlpath("/some/path.blosc2").unwrap();
404        /// assert_eq!(storage.get_urlpath().unwrap().unwrap(), "/some/path.blosc2");
405        /// ```
406        pub fn get_urlpath(&self) -> Result<Option<&str>> {
407            if self.inner.urlpath.is_null() {
408                return Ok(None);
409            }
410            unsafe {
411                CStr::from_ptr(self.inner.urlpath)
412                    .to_str()
413                    .map(|v| Some(v))
414                    .map_err(|e| Error::Other(e.to_string()))
415            }
416        }
417        /// Set the contiguous nature of the `schunk`.
418        pub fn set_contiguous(mut self, contiguous: bool) -> Self {
419            self.inner.contiguous = contiguous;
420            self
421        }
422        /// Set compression parameters
423        pub fn set_cparams(mut self, cparams: CParams) -> Self {
424            self.cparams = cparams;
425            self.inner.cparams = &mut self.cparams.0;
426            self
427        }
428        /// Get compression parameters
429        pub fn get_cparams(&self) -> &CParams {
430            &self.cparams
431        }
432        /// Set decompression parameters
433        pub fn set_dparams(mut self, dparams: DParams) -> Self {
434            self.dparams = dparams;
435            self.inner.dparams = &mut self.dparams.0;
436            self
437        }
438    }
439
440    /// Wraps a single chunk of a super-chunk.
441    ///
442    /// Normally constructed via `Chunk::from_schunk`
443    ///
444    /// Example
445    /// -------
446    /// ```
447    /// use std::io::Write;
448    /// use blosc2::{CParams, DParams, Blosc2Guard};
449    /// use blosc2::schunk::{Storage, SChunk, Chunk};
450    ///
451    /// let _guard = Blosc2Guard::get_or_init();
452    ///
453    /// let input = b"some data";
454    /// let storage = Storage::default()
455    ///     .set_contiguous(true)
456    ///     .set_cparams(CParams::from(&input[0]))
457    ///     .set_dparams(DParams::default());
458    /// let mut schunk = SChunk::new(storage);
459    ///
460    /// let n = schunk.write(input).unwrap();  // same as schunk.append_buffer(input)?;
461    /// assert_eq!(n as usize, input.len());
462    /// assert_eq!(schunk.n_chunks(), 1);
463    ///
464    /// let chunk = Chunk::from_schunk(&mut schunk, 0).unwrap();  // Get first (and only) chunk
465    /// assert_eq!(chunk.info().unwrap().nbytes() as usize, input.len());
466    /// ```
467    #[derive(Clone)]
468    pub struct Chunk {
469        pub(crate) chunk: Arc<RwLock<*mut u8>>,
470        pub(crate) needs_free: bool,
471    }
472
473    unsafe impl Sync for Chunk {}
474    unsafe impl Send for Chunk {}
475
476    impl TryFrom<Vec<u8>> for Chunk {
477        type Error = Error;
478        #[inline]
479        fn try_from(v: Vec<u8>) -> Result<Self> {
480            Self::from_vec(v)
481        }
482    }
483
484    impl Chunk {
485        /// Create a new `Chunk` directly from a pointer, you probably
486        /// want `Chunk::from_schunk` instead.
487        pub fn new(chunk: *mut u8, needs_free: bool) -> Self {
488            Self {
489                chunk: Arc::new(RwLock::new(chunk)),
490                needs_free,
491            }
492        }
493
494        /// Construct Chunk from vector of bytes, this Vec is assumed to be the result of a valid
495        /// chunk de/compression or other initialization method like uinit/zeros etc.
496        ///
497        /// Example
498        /// -------
499        /// ```
500        /// use blosc2::{schunk::Chunk, compress};
501        ///
502        /// let buf: Vec<u8> = compress(&vec![0i32, 1, 2, 3, 4], None, None, None, None).unwrap();
503        /// assert!(Chunk::from_vec(buf).is_ok());
504        /// ```
505        #[inline]
506        pub fn from_vec(v: Vec<u8>) -> Result<Self> {
507            let mut v = v;
508            v.shrink_to_fit();
509            if let Err(_) = CompressedBufferInfo::try_from(v.as_slice()) {
510                return Err("Appears this buffer is not a valid blosc2 chunk".into());
511            }
512            let ptr = v.as_mut_ptr();
513            mem::forget(v);
514            Ok(Self::new(ptr as _, true))
515        }
516
517        /// Export this Chunk into a `Vec<u8>`
518        /// Maybe clone underlying vec if it's managed by blosc2
519        pub fn into_vec(self) -> Result<Vec<u8>> {
520            let info = self.info()?;
521            let buf =
522                unsafe { Vec::from_raw_parts(*self.chunk.write(), info.cbytes(), info.cbytes()) };
523            if !self.needs_free {
524                return Ok(buf.clone());
525            }
526            Ok(buf)
527        }
528
529        /// Get the raw buffer of this chunk
530        pub fn as_slice(&self) -> Result<&[u8]> {
531            let info = CompressedBufferInfo::try_from(*self.chunk.read() as *const c_void)?;
532            let slice = unsafe { std::slice::from_raw_parts(*self.chunk.read(), info.cbytes()) };
533            Ok(slice)
534        }
535
536        /// Return the number of elements in the compressed Chunk
537        ///
538        /// Example
539        /// -------
540        /// ```
541        /// use blosc2::{schunk::Chunk, compress};
542        ///
543        /// let chunk: Chunk = compress(&vec![0i32, 1, 2, 3, 4], None, None, None, None)
544        ///    .unwrap()
545        ///    .try_into()
546        ///    .unwrap();
547        /// assert_eq!(chunk.len::<i32>().unwrap(), 5);
548        /// ```
549        #[inline]
550        pub fn len<T>(&self) -> Result<usize> {
551            CompressedBufferInfo::try_from(*self.chunk.read() as *const c_void)
552                .map(|info| info.nbytes() / mem::size_of::<T>())
553        }
554
555        #[inline]
556        pub fn getitems<T>(&self, offset: usize, n_items: usize) -> Result<Vec<T>> {
557            if self.len::<T>()? < offset + n_items {
558                return Err(format!(
559                    "Would be out of bounds. Chunk contains {} elements",
560                    self.len::<T>()?
561                )
562                .into());
563            }
564            getitems(self.as_slice()?, offset, n_items)
565        }
566
567        /// Create a chunk made of uninitialized values
568        ///
569        /// Examplek
570        /// -------
571        /// ```
572        /// use blosc2::CParams;
573        /// use blosc2::schunk::Chunk;
574        ///
575        /// let chunk = Chunk::uninit::<u8>(CParams::default(), 10).unwrap();
576        /// assert_eq!(chunk.info().unwrap().nbytes(), 10);
577        /// ```
578        pub fn uninit<T>(cparams: CParams, len: usize) -> Result<Self> {
579            let mut cparams = cparams;
580            if mem::size_of::<T>() != cparams.0.typesize as usize {
581                cparams.0.typesize = mem::size_of::<T>() as _;
582            }
583            let mut dst = Vec::with_capacity(
584                (len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
585            );
586            let nbytes = unsafe {
587                ffi::blosc2_chunk_uninit(
588                    cparams.0,
589                    len as i32 * cparams.0.typesize,
590                    dst.as_mut_ptr() as *mut c_void,
591                    dst.capacity() as i32,
592                )
593            };
594            if nbytes < 0 {
595                return Err("Failed to create uninitialized chunk".into());
596            }
597            unsafe { dst.set_len(nbytes as _) };
598            Self::from_vec(dst)
599        }
600
601        /// Create a chunk made of repeating a value
602        ///
603        /// Examplek
604        /// -------
605        /// ```
606        /// use blosc2::CParams;
607        /// use blosc2::schunk::Chunk;
608        ///
609        /// let mut chunk = Chunk::repeatval::<f32>(CParams::default().set_typesize::<f32>(), 0.123, 4).unwrap();
610        /// assert_eq!(chunk.info().unwrap().nbytes(), 16);  // 4 elements * 4 bytes each
611        /// assert_eq!(chunk.decompress::<f32>().unwrap(), vec![0.123_f32, 0.123, 0.123, 0.123]);
612        /// ```
613        pub fn repeatval<T>(cparams: CParams, value: T, len: usize) -> Result<Self> {
614            let mut cparams = cparams;
615            if mem::size_of::<T>() != cparams.0.typesize as usize {
616                cparams.0.typesize = mem::size_of::<T>() as _;
617            }
618            let mut dst = Vec::with_capacity(
619                (len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
620            );
621            let nbytes = unsafe {
622                ffi::blosc2_chunk_repeatval(
623                    cparams.0,
624                    len as i32 * cparams.0.typesize,
625                    dst.as_mut_ptr() as _,
626                    dst.capacity() as _,
627                    &value as *const T as _,
628                )
629            };
630            if nbytes < 0 {
631                return Err("Failed to create chunk".into());
632            }
633            unsafe { dst.set_len(nbytes as _) };
634            Self::from_vec(dst)
635        }
636
637        /// Create a chunk made of zeros
638        ///
639        /// Example
640        /// -------
641        /// ```
642        /// use blosc2::CParams;
643        /// use blosc2::schunk::Chunk;
644        ///
645        /// let cparams = CParams::default().set_typesize::<u32>();
646        /// let chunk = Chunk::zeros::<u32>(cparams, 10).unwrap();
647        /// assert_eq!(chunk.info().unwrap().nbytes(), 40);  // 10 elements * 4 bytes each
648        /// ```
649        pub fn zeros<T>(cparams: CParams, len: usize) -> Result<Self> {
650            let mut cparams = cparams;
651            if mem::size_of::<T>() != cparams.0.typesize as usize {
652                cparams.0.typesize = mem::size_of::<T>() as _;
653            }
654            let mut dst = Vec::with_capacity(
655                (len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
656            );
657
658            let nbytes = unsafe {
659                ffi::blosc2_chunk_zeros(
660                    cparams.0,
661                    len as i32 * cparams.0.typesize,
662                    dst.as_mut_ptr() as _,
663                    dst.capacity() as i32,
664                )
665            };
666            if nbytes < 0 {
667                return Err(Error::Blosc2(Blosc2Error::from(nbytes)));
668            }
669            unsafe { dst.set_len(nbytes as usize) };
670            Self::from_vec(dst)
671        }
672
673        /// Compression ratio of the `Chunk`
674        ///
675        /// Example
676        /// -------
677        /// ```
678        /// use blosc2::schunk::Chunk;
679        ///
680        /// let chunk = Chunk::compress(&vec![0i32; 1_000], None, None, None, None).unwrap();
681        /// let ratio = chunk.compression_ratio().unwrap();
682        /// assert_eq!(ratio, 125.0);
683        /// ```
684        pub fn compression_ratio(&self) -> Result<f32> {
685            let info = self.info()?;
686            if info.cbytes() == 0 {
687                return Ok(0f32);
688            }
689            Ok(info.nbytes() as f32 / info.cbytes() as f32)
690        }
691
692        /// Helper method to construct a `Chunk` directly from compression.
693        /// This is equivelent to:
694        /// ```
695        /// use blosc2::{compress, schunk::Chunk};
696        ///
697        /// let compressed = compress(&vec![0u8, 1, 2, 3], None, None, None, None).unwrap();
698        /// let chunk = Chunk::from_vec(compressed).unwrap();
699        /// assert_eq!(chunk.len::<u8>().unwrap(), 4);
700        /// ```
701        #[inline]
702        pub fn compress<T: 'static>(
703            src: &[T],
704            typesize: Option<usize>,
705            clevel: Option<CLevel>,
706            filter: Option<Filter>,
707            codec: Option<Codec>,
708        ) -> Result<Self> {
709            crate::compress(src, typesize, clevel, filter, codec).map(Self::from_vec)?
710        }
711
712        /// Decompress the current chunk
713        ///
714        /// Example
715        /// -------
716        /// ```
717        /// use blosc2::CParams;
718        /// use blosc2::schunk::Chunk;
719        ///
720        /// let cparams = CParams::default().set_typesize::<i64>();
721        /// let mut chunk = Chunk::zeros::<i64>(cparams, 5).unwrap();
722        ///
723        /// assert_eq!(chunk.info().unwrap().nbytes(), 40);  // 5 elements * 64bit == 40bytes
724        /// let decompressed = chunk.decompress::<i64>().unwrap();
725        /// dbg!(decompressed.len());
726        /// assert_eq!(decompressed, vec![0i64; 5]);
727        /// ```
728        pub fn decompress<T>(&self) -> Result<Vec<T>> {
729            let slice =
730                unsafe { std::slice::from_raw_parts(*self.chunk.read(), self.info()?.cbytes) };
731            crate::decompress(slice)
732        }
733
734        /// Create a new `Chunk` from a `SChunk`
735        #[inline]
736        pub fn from_schunk(schunk: &mut SChunk, nchunk: usize) -> Result<Self> {
737            let mut chunk: *mut u8 = std::ptr::null_mut();
738            let mut needs_free: bool = false;
739            let rc = unsafe {
740                ffi::blosc2_schunk_get_chunk(
741                    *schunk.0.read(),
742                    nchunk as _,
743                    &mut chunk as _,
744                    &mut needs_free,
745                )
746            };
747            if rc < 0 {
748                return Err(Error::Blosc2(Blosc2Error::from(rc as i32)));
749            }
750            Ok(Self {
751                chunk: Arc::new(RwLock::new(chunk)),
752                needs_free,
753            })
754        }
755
756        /// Uncompressed bytes in this Chunk
757        pub fn nbytes(&self) -> Result<usize> {
758            self.info().map(|info| info.nbytes)
759        }
760
761        /// Compressed bytes in this Chunk
762        pub fn cbytes(&self) -> Result<usize> {
763            self.info().map(|info| info.cbytes)
764        }
765
766        /// Get `CompressedBufferInfo` for this chunk.
767        #[inline]
768        pub fn info(&self) -> Result<CompressedBufferInfo> {
769            let mut nbytes = 0;
770            let mut cbytes = 0;
771            let mut blocksize = 0;
772            let rc = unsafe {
773                ffi::blosc2_cbuffer_sizes(
774                    *self.chunk.read() as _,
775                    &mut nbytes,
776                    &mut cbytes,
777                    &mut blocksize,
778                )
779            };
780            if rc < 0 {
781                return Err(Blosc2Error::from(rc).into());
782            }
783            Ok(CompressedBufferInfo {
784                nbytes: nbytes as _,
785                cbytes: cbytes as _,
786                blocksize: blocksize as _,
787            })
788        }
789    }
790
791    impl Drop for Chunk {
792        fn drop(&mut self) {
793            // drop if needs freed and this is last strong ref
794            if self.needs_free && Arc::strong_count(&self.chunk) == 1 {
795                unsafe { blosc2_sys::libc::free(*self.chunk.write() as _) };
796            }
797        }
798    }
799
800    /// Wrapper to [blosc2_schunk]
801    ///
802    /// [blosc2_schunk]: blosc2_sys::blosc2_schunk
803    #[derive(Clone)]
804    pub struct SChunk(pub(crate) Arc<RwLock<*mut ffi::blosc2_schunk>>);
805
806    unsafe impl Send for SChunk {}
807
808    // Loosely inspired by blosc2-python implementation
809    impl SChunk {
810        pub fn new(storage: Storage) -> Self {
811            let mut storage = storage;
812            let schunk = unsafe { ffi::blosc2_schunk_new(&mut storage.inner) };
813            Self(Arc::new(RwLock::new(schunk)))
814        }
815
816        pub fn copy(&self) -> Self {
817            let schunk =
818                unsafe { ffi::blosc2_schunk_copy(*self.0.read(), (**self.0.read()).storage) };
819            Self(Arc::new(RwLock::new(schunk)))
820        }
821
822        pub fn frame(&self) -> Result<&[u8]> {
823            unsafe {
824                if (**self.0.read()).frame.is_null() {
825                    return Err(Error::from("schunk frame is null"));
826                }
827                let len = ffi::blosc2_schunk_frame_len(*self.0.read()) as usize;
828                let buf = std::slice::from_raw_parts((**self.0.read()).frame as _, len);
829                Ok(buf)
830            }
831        }
832
833        #[inline]
834        pub(crate) fn inner(&self) -> &ffi::blosc2_schunk {
835            unsafe { &(**self.0.read()) }
836        }
837
838        #[inline]
839        #[allow(dead_code)]
840        pub(crate) fn inner_mut(&mut self) -> &mut ffi::blosc2_schunk {
841            unsafe { &mut (**self.0.write()) }
842        }
843
844        /// Append data to SChunk, returning new number of chunks
845        #[inline]
846        pub fn append_buffer<T>(&mut self, data: &[T]) -> Result<usize> {
847            if data.is_empty() {
848                return Ok(self.inner().nchunks as usize);
849            }
850            let nbytes = mem::size_of::<T>() * data.len();
851            let typesize = self.typesize();
852            if nbytes % self.typesize() != 0 {
853                let msg = format!("Buffer ({nbytes}) not evenly divisible by typesize: {typesize}");
854                return Err(Error::Other(msg));
855            }
856            let nchunks = unsafe {
857                ffi::blosc2_schunk_append_buffer(*self.0.read(), data.as_ptr() as _, nbytes as _)
858            };
859            if nchunks < 0 {
860                return Err(Blosc2Error::from(nchunks as i32).into());
861            }
862            Ok(nchunks as _)
863        }
864
865        /// Decompress a chunk, returning number of elements of `T` written to output buffer
866        #[inline]
867        pub fn decompress_chunk<T>(&mut self, nchunk: usize, dst: &mut [T]) -> Result<usize> {
868            let chunk = Chunk::from_schunk(self, nchunk)?;
869            let info = chunk.info()?;
870            if dst.len() * mem::size_of::<T>() < info.nbytes as usize {
871                let msg = format!(
872                    "Not large enough, need {} bytes but got buffer w/ {} bytes of storage",
873                    info.nbytes,
874                    dst.len() * mem::size_of::<T>()
875                );
876                return Err(msg.into());
877            }
878
879            let ptr = dst.as_mut_ptr() as _;
880            let size = unsafe {
881                ffi::blosc2_schunk_decompress_chunk(
882                    *self.0.read(),
883                    nchunk as _,
884                    ptr,
885                    info.nbytes as _,
886                )
887            };
888
889            if size < 0 {
890                return Err(Blosc2Error::from(size).into());
891            } else if size == 0 {
892                let msg = format!("Non-initialized error decompressing chunk '{}'", nchunk);
893                return Err(msg.into());
894            } else {
895                Ok((size / mem::size_of::<T>() as i32) as _)
896            }
897        }
898
899        #[inline]
900        pub fn decompress_chunk_vec<T>(&mut self, nchunk: usize) -> Result<Vec<T>> {
901            let chunk = Chunk::from_schunk(self, nchunk)?;
902            chunk.decompress()
903        }
904
905        /// Set slice buffer
906        pub fn set_slice_buffer(&self, start: usize, stop: usize, buf: &[u8]) -> Result<()> {
907            if stop > self.len() {
908                return Err(Error::from(format!(
909                    "`stop`: {} must be less than len: {}",
910                    stop,
911                    self.len(),
912                )));
913            }
914
915            if buf.len() % self.typesize() != 0 {
916                return Err(Error::from(
917                    "Buffer is not evenly divisible by SChunk typesize",
918                ));
919            }
920
921            let rc = unsafe {
922                ffi::blosc2_schunk_set_slice_buffer(
923                    *self.0.write(),
924                    start as _,
925                    stop as _,
926                    buf.as_ptr() as *const _ as *mut _,
927                )
928            };
929            if rc != 0 {
930                return Err(Blosc2Error::from(rc).into());
931            }
932            Ok(())
933        }
934
935        /// Get uncompressed slice of data from start until stop. Returned as bytes, which
936        /// can be transmuted/casted into the concrete item type.
937        /// start/stop is by items, not by bytes
938        pub fn get_slice_buffer(&self, start: usize, stop: usize) -> Result<Vec<u8>> {
939            if stop > self.len() {
940                return Err(Error::from(format!(
941                    "Out of bounds. `stop`={}, is more than length={}",
942                    stop,
943                    self.len()
944                )));
945            }
946            if stop <= start {
947                return Err(Error::from("start must be less than stop"));
948            }
949            let nbytes = (stop - start) * self.typesize();
950            let mut buf = vec![0u8; nbytes];
951            let rc = unsafe {
952                ffi::blosc2_schunk_get_slice_buffer(
953                    *self.0.read(),
954                    start as _,
955                    stop as _,
956                    buf.as_mut_ptr() as _,
957                )
958            };
959            if rc != 0 {
960                return Err(Blosc2Error::from(rc).into());
961            }
962            Ok(buf)
963        }
964
965        /// Convenience method to `get_slice_buffer` which will transmute resulting bytes buffer into `Vec<T>` for you.
966        /// **NB** This will check T is same size as schunk's typesize so is _fairly_ safe.
967        pub fn get_slice_buffer_as_type<T>(&self, start: usize, stop: usize) -> Result<Vec<T>> {
968            if mem::size_of::<T>() != self.typesize() {
969                return Err(Error::from("Size of T does not match schunk typesize"));
970            }
971            let buf = self.get_slice_buffer(start, stop)?;
972            Ok(unsafe { mem::transmute(buf) })
973        }
974
975        pub fn get_chunk(&self, nchunk: usize) -> Result<Chunk> {
976            let mut needs_free = true;
977            let mut chunk = std::ptr::null_mut();
978            let nbytes = unsafe {
979                ffi::blosc2_schunk_get_chunk(
980                    *self.0.read(),
981                    nchunk as _,
982                    &mut chunk as _,
983                    &mut needs_free,
984                )
985            };
986            if nbytes < 0 {
987                Err(Blosc2Error::from(nbytes).into())
988            } else {
989                Ok(Chunk::new(chunk, needs_free))
990            }
991        }
992
993        /// Export this `SChunk` into a buffer
994        pub fn into_vec(self) -> Result<Vec<u8>> {
995            if self.is_empty() {
996                return Ok(vec![]);
997            }
998
999            unsafe { ffi::blosc2_schunk_avoid_cframe_free(*self.0.read(), true) };
1000
1001            let mut needs_free = true;
1002            let mut ptr: *mut u8 = std::ptr::null_mut();
1003            let len =
1004                unsafe { ffi::blosc2_schunk_to_buffer(*self.0.read(), &mut ptr, &mut needs_free) };
1005            if len < 0 {
1006                return Err(Blosc2Error::from(len as i32).into());
1007            }
1008
1009            let mut buf = unsafe { Vec::from_raw_parts(ptr, len as _, len as _) };
1010            if !needs_free {
1011                buf = buf.clone(); // Clone into new since blosc is about to free this one
1012            }
1013            Ok(buf)
1014        }
1015
1016        /// Create a Schunk from an owned `Vec<u8>`. Data will be owned by the Schunk and released
1017        /// via normal Rust semantics.
1018        pub fn from_vec(buf: Vec<u8>) -> Result<Self> {
1019            let mut buf = buf;
1020            let schunk =
1021                unsafe { ffi::blosc2_schunk_from_buffer(buf.as_mut_ptr(), buf.len() as _, false) };
1022            if schunk.is_null() {
1023                return Err(Error::from(
1024                    "Failed to get schunk from buffer; might not be valid buffer for schunk",
1025                ));
1026            }
1027            unsafe { ffi::blosc2_schunk_avoid_cframe_free(schunk, true) };
1028            mem::forget(buf); // blosc2
1029            Ok(Self(Arc::new(RwLock::new(schunk))))
1030        }
1031
1032        // --- PROPERTIES ---
1033
1034        /// Check if storage of Schunk is contiguous.
1035        pub fn is_contiguous(&self) -> bool {
1036            unsafe { (*(self.inner()).storage).contiguous }
1037        }
1038
1039        /// Check typesize
1040        pub fn typesize(&self) -> usize {
1041            self.inner().typesize as _
1042        }
1043
1044        /// Compression ratio of the Schunk
1045        pub fn compression_ratio(&self) -> f32 {
1046            if self.inner().cbytes == 0 {
1047                return 0f32;
1048            }
1049            self.inner().nbytes as f32 / self.inner().cbytes as f32
1050        }
1051
1052        /// Number of chunks
1053        pub fn n_chunks(&self) -> usize {
1054            self.inner().nchunks as _
1055        }
1056
1057        /// Chunk shape
1058        pub fn chunk_shape(&self) -> usize {
1059            (self.inner().chunksize / self.inner().typesize) as _
1060        }
1061
1062        /// blocksize
1063        pub fn blocksize(&self) -> usize {
1064            self.inner().blocksize as _
1065        }
1066
1067        /// Count of uncompressed bytes
1068        pub fn nbytes(&self) -> usize {
1069            self.inner().nbytes as _
1070        }
1071
1072        /// Count of compressed bytes
1073        pub fn cbytes(&self) -> usize {
1074            self.inner().cbytes as _
1075        }
1076
1077        /// Path where the Schunk is stored, if file backed.
1078        pub fn path(&self) -> Option<std::path::PathBuf> {
1079            let urlpath_ptr = unsafe { (*(self.inner().storage)).urlpath };
1080            if urlpath_ptr.is_null() {
1081                return None;
1082            }
1083            let urlpath = unsafe { CStr::from_ptr(urlpath_ptr) };
1084            urlpath.to_str().map(PathBuf::from).ok()
1085        }
1086
1087        /// Returns under of elements in Schunk (nbytes / typesize)
1088        pub fn len(&self) -> usize {
1089            (self.inner().nbytes / self.inner().typesize as i64) as usize
1090        }
1091
1092        pub fn is_empty(&self) -> bool {
1093            self.len() == 0
1094        }
1095    }
1096
1097    impl Drop for SChunk {
1098        fn drop(&mut self) {
1099            // drop if this is the only reference to pointer
1100            if Arc::strong_count(&self.0) == 1 && !(*self.0.read()).is_null() {
1101                unsafe { ffi::blosc2_schunk_free(*self.0.write()) };
1102            }
1103        }
1104    }
1105
1106    impl io::Write for SChunk {
1107        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1108            self.append_buffer(buf)?;
1109            Ok(buf.len())
1110        }
1111        fn flush(&mut self) -> io::Result<()> {
1112            Ok(())
1113        }
1114    }
1115
1116    /// struct only used for wrapping a mutable reference to a `SChunk`
1117    /// to support `std::io::Read` decoding for a SChunk.
1118    ///
1119    /// This isn't needed for encoding `std::io::Write` since we can directly
1120    /// write buffers into SChunk. However, for `Read`, we can't be certain the
1121    /// decompressed chunks will fit into the supplied `&mut [u8]` buffer provided
1122    /// during `Read::read`. So this struct exists only to hold those intermediate
1123    /// buffer and position variables and not clutter `SChunk` implementation.
1124    pub struct SChunkDecoder<'schunk> {
1125        pub(crate) schunk: &'schunk mut SChunk,
1126        pub(crate) buf: io::Cursor<Vec<u8>>,
1127        pub(crate) nchunk: usize,
1128    }
1129    impl<'schunk> SChunkDecoder<'schunk> {
1130        pub fn new(schunk: &'schunk mut SChunk) -> Self {
1131            Self {
1132                schunk,
1133                buf: io::Cursor::new(vec![]),
1134                nchunk: 0,
1135            }
1136        }
1137    }
1138
1139    impl<'schunk> io::Read for SChunkDecoder<'schunk> {
1140        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1141            // Cursor is at end of buffer, so we can refill
1142            if self.buf.position() as usize == self.buf.get_ref().len() {
1143                if self.nchunk >= self.schunk.n_chunks() {
1144                    return Ok(0);
1145                }
1146                self.buf.get_mut().truncate(0);
1147                self.buf.set_position(0);
1148
1149                // Get chunk and check if we can decompress directly into caller's buffer
1150                let chunk = Chunk::from_schunk(self.schunk, self.nchunk)?;
1151                let nbytes = chunk.info()?.nbytes();
1152
1153                if nbytes <= buf.len() {
1154                    self.schunk.decompress_chunk(self.nchunk, buf)?;
1155                    self.nchunk += 1;
1156                    return Ok(nbytes);
1157                } else {
1158                    self.buf.get_mut().resize(nbytes as _, 0u8);
1159                    let nbytes_written: usize = self
1160                        .schunk
1161                        .decompress_chunk(self.nchunk, self.buf.get_mut().as_mut_slice())?;
1162
1163                    // These should always be equal, otherwise blosc2 gave the wrong expected
1164                    // uncompressed size of this chunk.
1165                    debug_assert_eq!(nbytes_written, nbytes);
1166                }
1167                self.nchunk += 1;
1168            }
1169            self.buf.read(buf)
1170        }
1171    }
1172}
1173
1174/// Wrapper to [blosc2_cparams].  
1175/// Compression parameters.
1176///
1177/// A normal way to construct this is using `std::convert::From<&T>(val)`
1178/// so it will create with default parameters and the correct `typesize`.
1179///
1180/// Example
1181/// -------
1182/// ```
1183/// use blosc2::CParams;
1184/// let values = vec![0u8, 1, 2, 3];
1185/// let cparams = CParams::new::<u8>()
1186///     .set_nthreads(2);  // Optionally adjust default values
1187/// ```
1188/// [blosc2_cparams]: blosc2_sys::blosc2_cparams
1189pub struct CParams(ffi::blosc2_cparams);
1190
1191impl CParams {
1192    pub fn new<T>() -> Self {
1193        Self::default().set_typesize::<T>()
1194    }
1195    pub fn from_typesize(typesize: usize) -> Self {
1196        let mut cparams = Self::default();
1197        cparams.0.typesize = typesize as _;
1198        cparams
1199    }
1200    pub(crate) fn into_inner(self) -> ffi::blosc2_cparams {
1201        self.0
1202    }
1203    #[allow(dead_code)]
1204    pub(crate) fn inner_ref_mut(&mut self) -> &mut ffi::blosc2_cparams {
1205        &mut self.0
1206    }
1207    /// Set codec, defaults to [Codec::BloscLz]
1208    ///
1209    /// [Codec::BloscLz]: crate::Codec::BloscLz
1210    pub fn set_codec(mut self, codec: Codec) -> Self {
1211        self.0.compcode = codec as _;
1212        self
1213    }
1214    /// Set clevel, defaults to [CLevel::Nine]
1215    ///
1216    /// [CLevel::Nine]: crate::CLevel::Nine
1217    pub fn set_clevel(mut self, clevel: CLevel) -> Self {
1218        self.0.clevel = clevel as _;
1219        self
1220    }
1221    /// Set filter, defaults to [Filter::Shuffle]
1222    ///
1223    /// [Filter::Shuffle]: crate::Filter::Shuffle
1224    pub fn set_filter(mut self, filter: Filter) -> Self {
1225        self.0.filters[ffi::BLOSC2_MAX_FILTERS as usize - 1] = filter as _;
1226        self
1227    }
1228    /// Set number of threads, defaults to 1
1229    pub fn set_nthreads(mut self, n: usize) -> Self {
1230        self.0.nthreads = n as _;
1231        self
1232    }
1233    /// Get number of threads
1234    pub fn get_nthreads(&self) -> usize {
1235        self.0.nthreads as _
1236    }
1237    /// Set the type size
1238    pub fn set_typesize<T>(mut self) -> Self {
1239        self.0.typesize = mem::size_of::<T>() as _;
1240        self
1241    }
1242    pub fn get_typesize(&self) -> usize {
1243        self.0.typesize as _
1244    }
1245}
1246
1247impl Default for CParams {
1248    #[inline]
1249    fn default() -> Self {
1250        let cparams = unsafe { ffi::blosc2_get_blosc2_cparams_defaults() };
1251        Self(cparams)
1252    }
1253}
1254
1255/// Create CParams from a reference to the type being compressed
1256impl<T> From<&T> for CParams {
1257    fn from(_: &T) -> Self {
1258        let mut cparams = CParams::default();
1259        cparams.0.typesize = mem::size_of::<T>() as _;
1260        cparams
1261    }
1262}
1263
1264/// Wrapper to [blosc2_dparams].  
1265/// Decompression parameters, normally constructed via `DParams::default()`.
1266///
1267/// Example
1268/// -------
1269/// ```
1270/// use blosc2::DParams;
1271/// let dparams = DParams::default()
1272///     .set_nthreads(2);  // Optionally adjust default values
1273/// ```
1274///
1275/// [blosc2_dparams]: blosc2_sys::blosc2_dparams
1276pub struct DParams(pub(crate) ffi::blosc2_dparams);
1277
1278impl DParams {
1279    /// Set number of theads for decompression, defaults to 1
1280    pub fn set_nthreads(mut self, n: usize) -> Self {
1281        self.0.nthreads = n as _;
1282        self
1283    }
1284    pub fn get_nthreads(&self) -> usize {
1285        self.0.nthreads as _
1286    }
1287}
1288
1289impl Default for DParams {
1290    #[inline]
1291    fn default() -> Self {
1292        let dparams = unsafe { ffi::blosc2_get_blosc2_dparams_defaults() };
1293        Self(dparams)
1294    }
1295}
1296
1297/// Wrapper to [blosc2_context].  
1298/// Container struct for de/compression ops requiring context when used in multithreaded environments
1299///
1300/// [blosc2_context]: blosc2_sys::blosc2_context
1301#[derive(Clone)]
1302pub struct Context(pub(crate) *mut ffi::blosc2_context);
1303
1304impl Context {
1305    /// Get the CParams from this context
1306    ///
1307    /// Example
1308    /// -------
1309    /// ```
1310    /// use blosc2::{Context, CParams};
1311    ///
1312    /// let ctx = Context::from(CParams::default().set_typesize::<i64>());
1313    /// assert_eq!(ctx.get_cparams().unwrap().get_typesize(), 8);
1314    /// ```
1315    pub fn get_cparams(&self) -> Result<CParams> {
1316        if self.0.is_null() {
1317            return Err("Context pointer is null".into());
1318        }
1319        let mut cparams = CParams::default();
1320        let rc = unsafe { ffi::blosc2_ctx_get_cparams(self.0, &mut cparams.0) };
1321        if rc < 0 {
1322            return Err(Blosc2Error::from(rc).into());
1323        }
1324        Ok(cparams)
1325    }
1326    /// Get the DParams from this context
1327    ///
1328    /// Example
1329    /// -------
1330    /// ```
1331    /// use blosc2::{Context, DParams};
1332    ///
1333    /// let ctx = Context::from(DParams::default().set_nthreads(12));
1334    /// assert_eq!(ctx.get_dparams().unwrap().get_nthreads(), 12);
1335    /// ```
1336    pub fn get_dparams(&self) -> Result<DParams> {
1337        if self.0.is_null() {
1338            return Err("Context pointer is null".into());
1339        }
1340        let mut dparams = DParams::default();
1341        let rc = unsafe { ffi::blosc2_ctx_get_dparams(self.0, &mut dparams.0) };
1342        if rc < 0 {
1343            return Err(Blosc2Error::from(rc).into());
1344        }
1345        Ok(dparams)
1346    }
1347}
1348
1349impl From<DParams> for Context {
1350    fn from(dparams: DParams) -> Self {
1351        Self(unsafe { ffi::blosc2_create_dctx(dparams.0) })
1352    }
1353}
1354impl From<CParams> for Context {
1355    fn from(cparams: CParams) -> Self {
1356        Self(unsafe { ffi::blosc2_create_cctx(cparams.0) })
1357    }
1358}
1359
1360impl Default for Context {
1361    fn default() -> Self {
1362        let ctx = unsafe { ffi::blosc2_create_cctx(CParams::default().into_inner()) };
1363        Self(ctx)
1364    }
1365}
1366
1367impl Drop for Context {
1368    fn drop(&mut self) {
1369        if !self.0.is_null() {
1370            unsafe { ffi::blosc2_free_ctx(self.0) };
1371        }
1372    }
1373}
1374
1375/// Info about a compressed buffer
1376/// Normal construction via `CompressedBufferInfo::try_from(&[u8])?`
1377#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1378pub struct CompressedBufferInfo {
1379    /// Number of bytes decompressed
1380    nbytes: usize,
1381    /// Number of bytes to be read from compressed buffer
1382    cbytes: usize,
1383    /// Used internally by blosc2 when compressing the blocks, exposed here for completion.
1384    /// You probably won't need it.
1385    blocksize: usize,
1386}
1387
1388impl CompressedBufferInfo {
1389    pub fn nbytes(&self) -> usize {
1390        self.nbytes
1391    }
1392    pub fn cbytes(&self) -> usize {
1393        self.cbytes
1394    }
1395    pub fn blocksize(&self) -> usize {
1396        self.blocksize
1397    }
1398}
1399
1400impl<T> TryFrom<&[T]> for CompressedBufferInfo {
1401    type Error = Error;
1402
1403    #[inline]
1404    fn try_from(buf: &[T]) -> Result<Self> {
1405        let mut nbytes = 0i32;
1406        let mut cbytes = 0i32;
1407        let mut blocksize = 0i32;
1408        let code = unsafe {
1409            ffi::blosc2_cbuffer_sizes(
1410                buf.as_ptr() as *const c_void,
1411                &mut nbytes as *mut _,
1412                &mut cbytes as *mut _,
1413                &mut blocksize as *mut _,
1414            )
1415        };
1416        if code < 0 {
1417            return Err(Blosc2Error::from(code).into());
1418        }
1419        Ok(CompressedBufferInfo {
1420            nbytes: nbytes as _,
1421            cbytes: cbytes as _,
1422            blocksize: blocksize as _,
1423        })
1424    }
1425}
1426impl TryFrom<*const c_void> for CompressedBufferInfo {
1427    type Error = Error;
1428
1429    #[inline]
1430    fn try_from(ptr: *const c_void) -> Result<Self> {
1431        let mut nbytes = 0i32;
1432        let mut cbytes = 0i32;
1433        let mut blocksize = 0i32;
1434        let code = unsafe {
1435            ffi::blosc2_cbuffer_sizes(
1436                ptr,
1437                &mut nbytes as *mut _,
1438                &mut cbytes as *mut _,
1439                &mut blocksize as *mut _,
1440            )
1441        };
1442        if code < 0 {
1443            return Err(Blosc2Error::from(code).into());
1444        }
1445        Ok(CompressedBufferInfo {
1446            nbytes: nbytes as _,
1447            cbytes: cbytes as _,
1448            blocksize: blocksize as _,
1449        })
1450    }
1451}
1452
1453/// Retrieve a number of elements from a `Chunk`
1454///
1455/// Example
1456/// -------
1457/// ```
1458/// use blosc2::{getitems, compress};
1459///
1460/// let chunk = compress(&vec![0u32, 1, 2, 3, 4], None, None, None, None).unwrap();
1461///
1462/// let offset = 1;
1463/// let n_items = 2;
1464/// let items = getitems::<u32>(&chunk, offset, n_items).unwrap();
1465/// assert_eq!(items, vec![1u32, 2]);
1466/// ```
1467#[inline]
1468pub fn getitems<T>(src: &[u8], offset: usize, n_items: usize) -> Result<Vec<T>> {
1469    let mut dst = Vec::with_capacity(n_items);
1470    let nbytes = unsafe {
1471        ffi::blosc2_getitem(
1472            src.as_ptr() as *const c_void,
1473            src.len() as _,
1474            offset as _,
1475            n_items as _,
1476            dst.as_mut_ptr() as *mut c_void,
1477            (n_items * mem::size_of::<T>()) as i32,
1478        )
1479    };
1480    if nbytes < 0 {
1481        return Err(Blosc2Error::from(nbytes).into());
1482    }
1483    unsafe { dst.set_len(nbytes as usize / mem::size_of::<T>()) };
1484    Ok(dst)
1485}
1486
1487/// Get a list of supported compressors in this build
1488#[inline]
1489pub fn list_compressors() -> Result<Vec<Codec>> {
1490    let names = unsafe {
1491        let ptr = ffi::blosc2_list_compressors();
1492        CStr::from_ptr(ptr)
1493            .to_str()
1494            .map(ToString::to_string)
1495            .map_err(|e| Error::Other(e.to_string()))
1496    }?;
1497
1498    let mut compressors = vec![];
1499    for name in names.split(',') {
1500        compressors.push(Codec::try_from(name)?);
1501    }
1502    Ok(compressors)
1503}
1504
1505/// Context interface to compression, does not require call to init/destroy. For
1506/// use in multithreaded applications
1507#[inline]
1508pub fn compress_ctx<T>(src: &[T], ctx: &mut Context) -> Result<Vec<u8>> {
1509    if src.is_empty() {
1510        return Ok(vec![]);
1511    }
1512    let mut dst =
1513        vec![0u8; max_compress_len(src, ctx.get_cparams().ok().map(|c| c.get_typesize()))];
1514    let size = compress_into_ctx(src, &mut dst, ctx)?;
1515    if dst.len() > size {
1516        dst.truncate(size as _);
1517    }
1518    Ok(dst)
1519}
1520
1521#[inline]
1522pub fn compress_into_ctx<T>(src: &[T], dst: &mut [u8], ctx: &mut Context) -> Result<usize> {
1523    if src.is_empty() {
1524        return Ok(0);
1525    }
1526    let size = unsafe {
1527        ffi::blosc2_compress_ctx(
1528            ctx.0,
1529            src.as_ptr() as *const c_void,
1530            (src.len() * ctx.get_cparams()?.get_typesize()) as _,
1531            dst.as_mut_ptr() as *mut c_void,
1532            dst.len() as _,
1533        )
1534    };
1535
1536    if size == 0 {
1537        return Err(format!("Buffer is incompressible").into());
1538    } else if size < 0 {
1539        return Err(Blosc2Error::from(size).into());
1540    }
1541    Ok(size as _)
1542}
1543
1544/// Return the max size a compressed buffer needs to be to hold `src`
1545#[inline(always)]
1546pub fn max_compress_len<T>(src: &[T], typesize: Option<usize>) -> usize {
1547    (src.len() * typesize.unwrap_or_else(|| mem::size_of::<T>()))
1548        + ffi::BLOSC2_MAX_OVERHEAD as usize
1549}
1550
1551pub fn max_compress_len_bytes(len_bytes: usize) -> usize {
1552    len_bytes + ffi::BLOSC2_MAX_OVERHEAD as usize
1553}
1554
1555#[inline]
1556pub fn compress<T: 'static>(
1557    src: &[T],
1558    typesize: Option<usize>,
1559    clevel: Option<CLevel>,
1560    filter: Option<Filter>,
1561    codec: Option<Codec>,
1562) -> Result<Vec<u8>> {
1563    if src.is_empty() {
1564        return Ok(vec![]);
1565    }
1566    let mut dst = Vec::with_capacity(max_compress_len(src, typesize));
1567    let typesize = typesize.unwrap_or_else(|| mem::size_of::<T>());
1568    set_compressor(codec.unwrap_or_default())?;
1569
1570    // If input is bytes, but typesize is >1 then we use src len directly
1571    // since blosc2_compress want's the length in bytes
1572    let multiplier = (&src[0] as &dyn std::any::Any)
1573        .downcast_ref::<u8>()
1574        .map(|_| 1)
1575        .unwrap_or(typesize);
1576
1577    let n_bytes = unsafe {
1578        ffi::blosc2_compress(
1579            clevel.unwrap_or_default() as _,
1580            filter.unwrap_or_default() as _,
1581            typesize as _,
1582            src.as_ptr() as *const c_void,
1583            (src.len() * multiplier) as _,
1584            dst.as_mut_ptr() as *mut c_void,
1585            dst.capacity() as _,
1586        )
1587    };
1588    if n_bytes < 0 {
1589        return Err(Blosc2Error::from(n_bytes).into());
1590    } else if n_bytes == 0 {
1591        return Err("Data is not compressable.".into());
1592    }
1593    unsafe { dst.set_len(n_bytes as _) };
1594    Ok(dst)
1595}
1596
1597#[inline]
1598pub fn compress_into<T: 'static>(
1599    src: &[T],
1600    dst: &mut [u8],
1601    typesize: Option<usize>,
1602    clevel: Option<CLevel>,
1603    filter: Option<Filter>,
1604    codec: Option<Codec>,
1605) -> Result<usize> {
1606    if src.is_empty() {
1607        return Ok(0);
1608    }
1609    let typesize = typesize.unwrap_or_else(|| mem::size_of::<T>());
1610    set_compressor(codec.unwrap_or_default())?;
1611
1612    // If input is bytes, but typesize is >1 then we use src len directly
1613    // since blosc2_compress want's the length in bytes
1614    let multiplier = (&src[0] as &dyn std::any::Any)
1615        .downcast_ref::<u8>()
1616        .map(|_| 1)
1617        .unwrap_or(typesize);
1618
1619    let n_bytes = unsafe {
1620        ffi::blosc2_compress(
1621            clevel.unwrap_or_default() as _,
1622            filter.unwrap_or_default() as _,
1623            typesize as _,
1624            src.as_ptr() as *const c_void,
1625            (src.len() * multiplier) as _,
1626            dst.as_mut_ptr() as *mut c_void,
1627            dst.len() as _,
1628        )
1629    };
1630    if n_bytes < 0 {
1631        return Err(Blosc2Error::from(n_bytes).into());
1632    } else if n_bytes == 0 {
1633        return Err("Data is not compressable.".into());
1634    }
1635
1636    Ok(n_bytes as _)
1637}
1638
1639#[inline]
1640pub fn decompress_ctx<T>(src: &[u8], ctx: &mut Context) -> Result<Vec<T>> {
1641    if src.is_empty() {
1642        return Ok(vec![]);
1643    }
1644    let info = CompressedBufferInfo::try_from(src)?;
1645    let n_elements = info.nbytes as usize / mem::size_of::<T>();
1646    let mut dst = Vec::with_capacity(n_elements);
1647
1648    let n_bytes = unsafe {
1649        ffi::blosc2_decompress_ctx(
1650            ctx.0,
1651            src.as_ptr() as *const c_void,
1652            src.len() as i32,
1653            dst.as_mut_ptr() as *mut c_void,
1654            info.nbytes as _,
1655        )
1656    };
1657
1658    if n_bytes < 0 {
1659        return Err(Blosc2Error::from(n_bytes).into());
1660    }
1661    debug_assert_eq!(n_bytes as usize, info.nbytes);
1662    unsafe { dst.set_len(n_elements) };
1663    Ok(dst)
1664}
1665
1666#[inline]
1667pub fn decompress_into_ctx<T>(src: &[T], dst: &mut [T], ctx: &mut Context) -> Result<usize> {
1668    if src.is_empty() {
1669        return Ok(0);
1670    }
1671    let info = CompressedBufferInfo::try_from(src)?;
1672    let n_bytes = unsafe {
1673        ffi::blosc2_decompress_ctx(
1674            ctx.0,
1675            src.as_ptr() as *const c_void,
1676            src.len() as i32,
1677            dst.as_mut_ptr() as *mut c_void,
1678            info.nbytes as _,
1679        )
1680    };
1681
1682    if n_bytes < 0 {
1683        return Err(Blosc2Error::from(n_bytes).into());
1684    }
1685    debug_assert_eq!(n_bytes as usize, info.nbytes);
1686    Ok(n_bytes as _)
1687}
1688
1689/// Get the number of elements `T` in the compressed chunk
1690#[inline]
1691pub fn len<T>(src: &[u8]) -> Result<usize> {
1692    let info = CompressedBufferInfo::try_from(src)?;
1693    Ok(info.nbytes() / mem::size_of::<T>())
1694}
1695
1696#[inline]
1697pub fn decompress<T>(src: &[u8]) -> Result<Vec<T>> {
1698    if src.is_empty() {
1699        return Ok(vec![]);
1700    }
1701
1702    // blosc2 plays by bytes, we'll go by however many bytes per element
1703    // to set the vec length in actual elements
1704    let info = CompressedBufferInfo::try_from(src)?;
1705    let n_elements = info.nbytes as usize / mem::size_of::<T>();
1706    let mut dst = Vec::with_capacity(n_elements);
1707
1708    let n_bytes = unsafe {
1709        ffi::blosc2_decompress(
1710            src.as_ptr() as *const c_void,
1711            src.len() as i32,
1712            dst.as_mut_ptr() as *mut c_void,
1713            info.nbytes as _,
1714        )
1715    };
1716
1717    if n_bytes < 0 {
1718        return Err(Blosc2Error::from(n_bytes).into());
1719    }
1720
1721    debug_assert_eq!(n_bytes as usize, info.nbytes);
1722    unsafe { dst.set_len(n_elements) };
1723
1724    Ok(dst)
1725}
1726
1727#[inline]
1728pub fn decompress_into<T>(src: &[u8], dst: &mut [T]) -> Result<usize> {
1729    if src.is_empty() {
1730        return Ok(0);
1731    }
1732    let info = CompressedBufferInfo::try_from(src)?;
1733    let n_bytes = unsafe {
1734        ffi::blosc2_decompress(
1735            src.as_ptr() as *const c_void,
1736            src.len() as i32,
1737            dst.as_mut_ptr() as *mut c_void,
1738            info.nbytes as _,
1739        )
1740    };
1741
1742    if n_bytes < 0 {
1743        return Err(Blosc2Error::from(n_bytes).into());
1744    }
1745    debug_assert_eq!(n_bytes as usize, info.nbytes);
1746    Ok(n_bytes as _)
1747}
1748
1749#[inline]
1750pub fn set_compressor(codec: Codec) -> Result<()> {
1751    let codec_name = codec.to_name_cstring()?;
1752    let rc = unsafe { ffi::blosc1_set_compressor(codec_name.as_ptr()) };
1753    if rc < 0 {
1754        Err(Blosc2Error::from(rc).into())
1755    } else {
1756        Ok(())
1757    }
1758}
1759
1760pub fn set_nthreads(nthreads: usize) -> usize {
1761    let n = unsafe { ffi::blosc2_set_nthreads(nthreads as _) };
1762    n as _
1763}
1764pub fn get_nthreads() -> usize {
1765    unsafe { ffi::blosc2_get_nthreads() as _ }
1766}
1767
1768/// Get the version for a given Codec.
1769pub fn get_complib_info(codec: Codec) -> Result<String> {
1770    let mut compname = std::ptr::null();
1771    let mut complib = std::ptr::null_mut();
1772    let mut version = std::ptr::null_mut();
1773
1774    if unsafe { ffi::blosc2_compcode_to_compname(codec as i32, &mut compname) } == -1 {
1775        return Err("Codec not recognized".into());
1776    }
1777    if unsafe { ffi::blosc2_get_complib_info(compname as _, &mut complib, &mut version) } == -1 {
1778        return Err("Codec not supported".into());
1779    }
1780
1781    let info = unsafe {
1782        format!(
1783            "{}: {}",
1784            CString::from_raw(complib)
1785                .into_string()
1786                .map_err(|e| e.to_string())?,
1787            CString::from_raw(version)
1788                .into_string()
1789                .map_err(|e| e.to_string())?
1790        )
1791    };
1792    Ok(info)
1793}
1794
1795/// Get the Blosc2 version string
1796pub fn get_version_string() -> Result<String> {
1797    CStr::from_bytes_with_nul(ffi::BLOSC2_VERSION_STRING)
1798        .map_err(|e| e.to_string())?
1799        .to_str()
1800        .map_err(|e| e.to_string().into())
1801        .map(|s| s.to_string())
1802}
1803
1804/// Call before using blosc2, unless using specific ctx de/compression variants
1805/// This is a safe wrapper to `blosc2_init` where a global `Arc<Mutex<bool>>` is used to track
1806/// if blosc2 has already been initialized. For fine grained control use `init_unsafe` / `destroy_unsafe`
1807pub fn init() {
1808    let initd = BLOSC2_INIT_FLAG
1809        .get_or_init(|| Arc::new(Mutex::new(false)))
1810        .clone();
1811    let mut guard = initd.lock();
1812    if !*guard {
1813        unsafe { init_unsafe() };
1814        *guard = true;
1815    }
1816}
1817
1818/// Call before using blosc2, unless using specific ctx de/compression variants
1819/// For a safe interface managing the singleton nature of this call, use `init`/`destroy` functions.
1820pub unsafe fn init_unsafe() {
1821    unsafe { ffi::blosc2_init() }
1822}
1823
1824/// Call at end of using blosc2 library, unless you've never called `blosc2_init`
1825/// This is a safe wrapper to `blosc2_destroy` where a global `Arc<Mutex<bool>>` is used to track
1826/// if blosc2 has already been destroyed. For fine grained control use `init_unsafe` / `destroy_unsafe`
1827pub fn destroy() {
1828    let initd = BLOSC2_INIT_FLAG
1829        .get_or_init(|| Arc::new(Mutex::new(false)))
1830        .clone();
1831    let mut guard = initd.lock();
1832    if *guard {
1833        unsafe { destroy_unsafe() };
1834        *guard = false;
1835    }
1836}
1837
1838/// Call at end of using blosc2 library, unless you've never called `blosc2_init`
1839/// For a safe interface managing the singleton nature of this call, use `init`/`destroy` functions.
1840pub unsafe fn destroy_unsafe() {
1841    unsafe { ffi::blosc2_destroy() };
1842}
1843
1844/// Singleton struct for initializing blosc2 and auto destroying it when going out of scope.
1845/// **Note**: if you create more than one of these, the *last* one dropped will cause a call to `blosc2_destroy`
1846pub struct Blosc2Guard;
1847
1848impl Blosc2Guard {
1849    /// Get or create the blosc2 initialization guard. If blosc2's init has already been called this
1850    /// will return the existing singleton otherwise will call init and return the singleton.
1851    pub fn get_or_init() -> Arc<Self> {
1852        init();
1853        BLOSC2_GUARD.get_or_init(|| Arc::new(Self {})).clone()
1854    }
1855}
1856
1857impl Drop for Blosc2Guard {
1858    fn drop(&mut self) {
1859        destroy();
1860    }
1861}
1862
1863/// Free possible memory temporaries and thread resources. Use this
1864/// when you are not going to use Blosc for a long while.
1865/// A 0 if succeeds, in case of problems releasing the resources,
1866/// it returns a negative number.
1867pub fn free_resources() -> Result<()> {
1868    let ret = unsafe { ffi::blosc2_free_resources() };
1869    if ret != 0 {
1870        Err(Error::Blosc2(Blosc2Error::from(ret)))
1871    } else {
1872        Ok(())
1873    }
1874}
1875
1876#[cfg(test)]
1877mod tests {
1878    use ctor::{ctor, dtor};
1879    use std::io::Cursor;
1880
1881    use super::*;
1882
1883    #[ctor]
1884    fn blosc2_init() {
1885        init();
1886    }
1887
1888    #[dtor]
1889    fn blosc2_destory() {
1890        destroy();
1891    }
1892
1893    #[test]
1894    fn test_compress_ctx() -> Result<()> {
1895        let input = b"some data";
1896        let compressed = compress_ctx(input, &mut Context::from(CParams::from(&input[0])))?;
1897        let decompressed = decompress(&compressed)?;
1898        assert_eq!(input, decompressed.as_slice());
1899        Ok(())
1900    }
1901
1902    #[test]
1903    fn test_compress_into_ctx() -> Result<()> {
1904        let input = b"some data";
1905        let mut compressed = vec![0u8; 100];
1906        let n_bytes = compress_into_ctx(
1907            input,
1908            &mut compressed,
1909            &mut Context::from(CParams::from(&input[0])),
1910        )?;
1911        let decompressed = decompress(&compressed[..n_bytes])?;
1912        assert_eq!(input, decompressed.as_slice());
1913        Ok(())
1914    }
1915
1916    #[test]
1917    fn test_decompress_ctx() -> Result<()> {
1918        let input = b"some data";
1919        let compressed = compress(input, None, None, None, None)?;
1920        let decompressed = decompress_ctx(&compressed, &mut Context::from(DParams::default()))?;
1921        assert_eq!(input, decompressed.as_slice());
1922        Ok(())
1923    }
1924
1925    #[test]
1926    fn test_decompress_into_ctx() -> Result<()> {
1927        let input = b"some data";
1928        let compressed = compress(input, None, None, None, None)?;
1929        let mut decompressed = vec![0u8; input.len()];
1930        let n_bytes = decompress_into_ctx(
1931            &compressed,
1932            &mut decompressed,
1933            &mut Context::from(DParams::default()),
1934        )?;
1935        assert_eq!(n_bytes, input.len());
1936        assert_eq!(input, decompressed.as_slice());
1937        Ok(())
1938    }
1939
1940    #[test]
1941    fn test_basic_roundtrip() -> Result<()> {
1942        let input = b"some data";
1943        let compressed = compress(input, None, None, None, None)?;
1944        let decompressed = decompress(&compressed)?;
1945        assert_eq!(input, decompressed.as_slice());
1946        Ok(())
1947    }
1948
1949    #[test]
1950    fn test_basic_roundtrip_into() -> Result<()> {
1951        let input = b"some data";
1952        let mut compressed = vec![0u8; 100];
1953        let n_bytes = compress_into(input, &mut compressed, None, None, None, None)?;
1954
1955        let mut decompressed = vec![0u8; input.len()];
1956        let n_out = decompress_into(&compressed[..n_bytes], &mut decompressed)?;
1957
1958        assert_eq!(n_out, input.len());
1959        assert_eq!(input, decompressed.as_slice());
1960        Ok(())
1961    }
1962
1963    #[test]
1964    fn test_schunk_basic() -> Result<()> {
1965        let input = b"some data";
1966        let storage = schunk::Storage::default()
1967            .set_contiguous(true)
1968            .set_cparams(CParams::from(&input[0]))
1969            .set_dparams(DParams::default());
1970        let mut schunk = schunk::SChunk::new(storage);
1971
1972        assert!(schunk.is_contiguous());
1973        assert_eq!(schunk.typesize(), 1);
1974        assert!(schunk.path().is_none());
1975
1976        let mut decompressed = vec![0u8; input.len()];
1977
1978        let n = schunk.append_buffer(input)?;
1979        schunk.decompress_chunk(n - 1, &mut decompressed)?;
1980        assert_eq!(input, decompressed.as_slice());
1981
1982        {
1983            // ensure clone then drop doesn't free the schunk ptr, original still needs is
1984            let _cloned = schunk.clone();
1985        }
1986        assert_eq!(schunk.n_chunks(), 1);
1987
1988        // Reconstruct thru vec
1989        let v = schunk.into_vec()?;
1990        schunk = schunk::SChunk::from_vec(v)?;
1991        assert_eq!(schunk.n_chunks(), 1);
1992
1993        Ok(())
1994    }
1995
1996    #[test]
1997    fn test_schunk_thread_shared() -> Result<()> {
1998        let input = b"some data";
1999        let storage = schunk::Storage::default()
2000            .set_contiguous(true)
2001            .set_cparams(CParams::from(&input[0]))
2002            .set_dparams(DParams::default());
2003        let mut schunk = schunk::SChunk::new(storage);
2004
2005        schunk.append_buffer(input)?;
2006
2007        let mut schunk2 = schunk.clone();
2008        std::thread::spawn(move || {
2009            assert_eq!(schunk2.n_chunks(), 1);
2010            schunk2.append_buffer(b"more data").unwrap();
2011        })
2012        .join()
2013        .unwrap();
2014
2015        assert_eq!(schunk.n_chunks(), 2);
2016        assert_eq!(
2017            b"some data",
2018            schunk.decompress_chunk_vec(0).unwrap().as_slice()
2019        );
2020        assert_eq!(
2021            b"more data",
2022            schunk.decompress_chunk_vec(1).unwrap().as_slice()
2023        );
2024
2025        Ok(())
2026    }
2027
2028    #[cfg(not(target_os = "windows"))]
2029    #[test]
2030    fn test_schunk_write() -> Result<()> {
2031        let input = std::iter::repeat(b"some data")
2032            .take(BUFSIZE)
2033            .flat_map(|v| v.to_vec())
2034            .collect::<Vec<u8>>();
2035        let storage = schunk::Storage::default()
2036            .set_contiguous(true)
2037            .set_cparams(CParams::from(&input[0]))
2038            .set_dparams(DParams::default());
2039        let mut schunk = schunk::SChunk::new(storage);
2040
2041        let nbytes = std::io::copy(&mut Cursor::new(input.clone()), &mut schunk)
2042            .map_err(|e| Error::Other(e.to_string()))?;
2043        assert_eq!(nbytes as usize, input.len());
2044
2045        let ratio = schunk.compression_ratio();
2046        assert!(84. < ratio);
2047        assert!(86. > ratio);
2048
2049        let mut uncompressed = vec![];
2050        let mut decoder = schunk::SChunkDecoder::new(&mut schunk);
2051        let n = std::io::copy(&mut decoder, &mut uncompressed).unwrap();
2052        assert_eq!(input, uncompressed.as_slice());
2053        assert_eq!(n as usize, input.len());
2054
2055        Ok(())
2056    }
2057
2058    #[cfg(not(target_os = "windows"))]
2059    #[test]
2060    fn test_get_version_string() -> Result<()> {
2061        let version = get_version_string()?;
2062        assert_eq!(&version, "2.15.1");
2063        Ok(())
2064    }
2065
2066    #[cfg(not(target_os = "windows"))]
2067    #[test]
2068    fn test_get_complib_version_string() -> Result<()> {
2069        let info = get_complib_info(Codec::BloscLz)?;
2070        assert_eq!(&info, "BloscLZ: 2.5.3");
2071        Ok(())
2072    }
2073
2074    #[test]
2075    fn test_list_compressors() {
2076        let compressors = list_compressors().unwrap();
2077        for compressor in &[
2078            Codec::BloscLz,
2079            Codec::LZ4,
2080            Codec::LZ4HC,
2081            Codec::ZLIB,
2082            Codec::ZSTD,
2083        ] {
2084            assert!(compressors.contains(compressor));
2085        }
2086    }
2087}