use blosc2_sys as ffi;
use parking_lot::RwLock;
use std::ffi::{c_void, CStr, CString};
use std::sync::Arc;
use std::{io, mem};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Blosc2(Blosc2Error),
Other(String),
}
impl From<Blosc2Error> for Error {
fn from(err: Blosc2Error) -> Self {
Error::Blosc2(err)
}
}
impl From<String> for Error {
fn from(err: String) -> Self {
Error::Other(err)
}
}
impl<'a> From<&'a str> for Error {
fn from(err: &'a str) -> Self {
Error::Other(err.to_string())
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, err.to_string())
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug)]
#[repr(i32)]
pub enum Blosc2Error {
Failure = ffi::BLOSC2_ERROR_FAILURE,
Stream = ffi::BLOSC2_ERROR_STREAM,
Data = ffi::BLOSC2_ERROR_DATA,
MemoryAlloc = ffi::BLOSC2_ERROR_MEMORY_ALLOC,
ReadBuffer = ffi::BLOSC2_ERROR_READ_BUFFER,
WriteBuffer = ffi::BLOSC2_ERROR_WRITE_BUFFER,
CodecSupport = ffi::BLOSC2_ERROR_CODEC_SUPPORT,
CodecParam = ffi::BLOSC2_ERROR_CODEC_PARAM,
CodecDict = ffi::BLOSC2_ERROR_CODEC_DICT,
VersionSupport = ffi::BLOSC2_ERROR_VERSION_SUPPORT,
InvalidHeader = ffi::BLOSC2_ERROR_INVALID_HEADER,
InvalidParam = ffi::BLOSC2_ERROR_INVALID_PARAM,
FileRead = ffi::BLOSC2_ERROR_FILE_READ,
FileWrite = ffi::BLOSC2_ERROR_FILE_WRITE,
FileOpen = ffi::BLOSC2_ERROR_FILE_OPEN,
NotFound = ffi::BLOSC2_ERROR_NOT_FOUND,
RunLength = ffi::BLOSC2_ERROR_RUN_LENGTH,
FilterPipeline = ffi::BLOSC2_ERROR_FILTER_PIPELINE,
ChunkInsert = ffi::BLOSC2_ERROR_CHUNK_INSERT,
ChunkAppend = ffi::BLOSC2_ERROR_CHUNK_APPEND,
ChunkUpdate = ffi::BLOSC2_ERROR_CHUNK_UPDATE,
TwoGBLimit = ffi::BLOSC2_ERROR_2GB_LIMIT,
SchunkCopy = ffi::BLOSC2_ERROR_SCHUNK_COPY,
FrameType = ffi::BLOSC2_ERROR_FRAME_TYPE,
FileTruncate = ffi::BLOSC2_ERROR_FILE_TRUNCATE,
ThreadCreate = ffi::BLOSC2_ERROR_THREAD_CREATE,
PostFilter = ffi::BLOSC2_ERROR_POSTFILTER,
FrameSpecial = ffi::BLOSC2_ERROR_FRAME_SPECIAL,
SchunkSpecial = ffi::BLOSC2_ERROR_SCHUNK_SPECIAL,
PluginIO = ffi::BLOSC2_ERROR_PLUGIN_IO,
FileRemove = ffi::BLOSC2_ERROR_FILE_REMOVE,
NullPointer = ffi::BLOSC2_ERROR_NULL_POINTER,
InvalidIndex = ffi::BLOSC2_ERROR_INVALID_INDEX,
MetalayerNotFound = ffi::BLOSC2_ERROR_METALAYER_NOT_FOUND,
}
impl std::fmt::Display for Blosc2Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl From<i32> for Blosc2Error {
fn from(code: i32) -> Self {
match code {
ffi::BLOSC2_ERROR_FAILURE => Blosc2Error::Failure,
ffi::BLOSC2_ERROR_STREAM => Blosc2Error::Stream,
ffi::BLOSC2_ERROR_DATA => Blosc2Error::Data,
ffi::BLOSC2_ERROR_MEMORY_ALLOC => Blosc2Error::MemoryAlloc,
ffi::BLOSC2_ERROR_READ_BUFFER => Blosc2Error::ReadBuffer,
ffi::BLOSC2_ERROR_WRITE_BUFFER => Blosc2Error::WriteBuffer,
ffi::BLOSC2_ERROR_CODEC_SUPPORT => Blosc2Error::CodecSupport,
ffi::BLOSC2_ERROR_CODEC_PARAM => Blosc2Error::CodecParam,
ffi::BLOSC2_ERROR_CODEC_DICT => Blosc2Error::CodecDict,
ffi::BLOSC2_ERROR_VERSION_SUPPORT => Blosc2Error::VersionSupport,
ffi::BLOSC2_ERROR_INVALID_HEADER => Blosc2Error::InvalidHeader,
ffi::BLOSC2_ERROR_INVALID_PARAM => Blosc2Error::InvalidParam,
ffi::BLOSC2_ERROR_FILE_READ => Blosc2Error::FileRead,
ffi::BLOSC2_ERROR_FILE_WRITE => Blosc2Error::FileWrite,
ffi::BLOSC2_ERROR_FILE_OPEN => Blosc2Error::FileOpen,
ffi::BLOSC2_ERROR_NOT_FOUND => Blosc2Error::NotFound,
ffi::BLOSC2_ERROR_RUN_LENGTH => Blosc2Error::RunLength,
ffi::BLOSC2_ERROR_FILTER_PIPELINE => Blosc2Error::FilterPipeline,
ffi::BLOSC2_ERROR_CHUNK_INSERT => Blosc2Error::ChunkInsert,
ffi::BLOSC2_ERROR_CHUNK_APPEND => Blosc2Error::ChunkAppend,
ffi::BLOSC2_ERROR_CHUNK_UPDATE => Blosc2Error::ChunkUpdate,
ffi::BLOSC2_ERROR_2GB_LIMIT => Blosc2Error::TwoGBLimit,
ffi::BLOSC2_ERROR_SCHUNK_COPY => Blosc2Error::SchunkCopy,
ffi::BLOSC2_ERROR_FRAME_TYPE => Blosc2Error::FrameType,
ffi::BLOSC2_ERROR_FILE_TRUNCATE => Blosc2Error::FileTruncate,
ffi::BLOSC2_ERROR_THREAD_CREATE => Blosc2Error::ThreadCreate,
ffi::BLOSC2_ERROR_POSTFILTER => Blosc2Error::PostFilter,
ffi::BLOSC2_ERROR_FRAME_SPECIAL => Blosc2Error::FrameSpecial,
ffi::BLOSC2_ERROR_SCHUNK_SPECIAL => Blosc2Error::SchunkSpecial,
ffi::BLOSC2_ERROR_PLUGIN_IO => Blosc2Error::PluginIO,
ffi::BLOSC2_ERROR_FILE_REMOVE => Blosc2Error::FileRemove,
ffi::BLOSC2_ERROR_NULL_POINTER => Blosc2Error::NullPointer,
ffi::BLOSC2_ERROR_INVALID_INDEX => Blosc2Error::InvalidIndex,
ffi::BLOSC2_ERROR_METALAYER_NOT_FOUND => Blosc2Error::MetalayerNotFound,
_ => panic!("Error code {} not matched in existing Error codes", code),
}
}
}
pub const BUFSIZE: usize = 8196_usize;
#[derive(Copy, Clone)]
pub enum Filter {
NoFilter = ffi::BLOSC_NOFILTER as _,
Shuffle = ffi::BLOSC_SHUFFLE as _,
BitShuffle = ffi::BLOSC_BITSHUFFLE as _,
Delta = ffi::BLOSC_DELTA as _,
TruncPrec = ffi::BLOSC_TRUNC_PREC as _,
LastFilter = ffi::BLOSC_LAST_FILTER as _,
LastRegisteredFilter = ffi::BLOSC_LAST_REGISTERED_FILTER as _,
}
impl Default for Filter {
fn default() -> Self {
Filter::Shuffle
}
}
impl ToString for Filter {
fn to_string(&self) -> String {
match self {
Self::NoFilter => "nofilter",
Self::Shuffle => "shuffle",
Self::BitShuffle => "bitshuffle",
Self::Delta => "delta",
Self::TruncPrec => "truncprec",
Self::LastFilter => "lastfilter",
Self::LastRegisteredFilter => "lastregisteredfilter",
}
.to_string()
}
}
impl<'a> TryFrom<&'a str> for Filter {
type Error = Error;
fn try_from(val: &'a str) -> Result<Self> {
match val.to_lowercase().as_str() {
"nofilter" => Ok(Filter::NoFilter),
"shuffle" => Ok(Filter::Shuffle),
"bitshuffle" => Ok(Filter::BitShuffle),
"delta" => Ok(Filter::Delta),
"trunctprec" => Ok(Filter::TruncPrec),
"lastfilter" => Ok(Filter::LastFilter),
"lastregisteredfilter" => Ok(Filter::LastRegisteredFilter),
_ => Err(Error::from(format!("No matching filter for '{}'", val))),
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum Codec {
BloscLz = ffi::BLOSC_BLOSCLZ as _,
LZ4 = ffi::BLOSC_LZ4 as _,
LZ4HC = ffi::BLOSC_LZ4HC as _,
ZLIB = ffi::BLOSC_ZLIB as _,
ZSTD = ffi::BLOSC_ZSTD as _,
LastCodec = ffi::BLOSC_LAST_CODEC as _,
LastRegisteredCodec = ffi::BLOSC_LAST_REGISTERED_CODEC as _,
}
impl Codec {
#[allow(dead_code)]
fn to_name(&self) -> Result<String> {
(*self).try_into()
}
fn to_name_cstring(&self) -> Result<CString> {
let mut compname = std::ptr::null();
let rc = unsafe { ffi::blosc2_compcode_to_compname(*self as _, &mut compname) };
if rc == -1 {
return Err(Error::Other(format!("Unsupported Codec {:?}", self)));
}
unsafe { Ok(CStr::from_ptr(compname as _).to_owned()) }
}
}
impl TryInto<String> for Codec {
type Error = Error;
fn try_into(self) -> Result<String> {
self.clone()
.to_name_cstring()?
.into_string()
.map_err(|e| Error::Other(e.to_string()))
}
}
impl<'a> TryFrom<&'a str> for Codec {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self> {
let compname = CString::new(value).map_err(|e| Error::Other(e.to_string()))?;
let compcode = unsafe { ffi::blosc2_compname_to_compcode(compname.as_ptr()) };
if compcode == -1 {
return Err(Error::from(format!("Compcode {} not recognized", value)));
}
Codec::try_from(compcode)
}
}
impl TryFrom<i32> for Codec {
type Error = Error;
fn try_from(compcode: i32) -> Result<Self> {
match compcode as _ {
ffi::BLOSC_BLOSCLZ => Ok(Codec::BloscLz),
ffi::BLOSC_LZ4 => Ok(Codec::LZ4),
ffi::BLOSC_LZ4HC => Ok(Codec::LZ4HC),
ffi::BLOSC_ZLIB => Ok(Codec::ZLIB),
ffi::BLOSC_ZSTD => Ok(Codec::ZSTD),
ffi::BLOSC_LAST_CODEC => Ok(Codec::LastCodec),
ffi::BLOSC_LAST_REGISTERED_CODEC => Ok(Codec::LastRegisteredCodec),
_ => Err(format!("Not match for compcode {}", compcode).into()),
}
}
}
impl Default for Codec {
fn default() -> Self {
Codec::BloscLz
}
}
#[repr(u8)]
pub enum CLevel {
Zero = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
}
impl Default for CLevel {
fn default() -> Self {
CLevel::Nine }
}
impl TryFrom<usize> for CLevel {
type Error = Error;
fn try_from(val: usize) -> Result<Self> {
match val {
0 => Ok(CLevel::Zero),
1 => Ok(CLevel::One),
2 => Ok(CLevel::Two),
3 => Ok(CLevel::Three),
4 => Ok(CLevel::Four),
5 => Ok(CLevel::Five),
6 => Ok(CLevel::Six),
7 => Ok(CLevel::Seven),
8 => Ok(CLevel::Eight),
9 => Ok(CLevel::Nine),
_ => Err(Error::from(format!(
"Compression level must be 0-9, got {}",
val
))),
}
}
}
pub mod schunk {
use std::ffi::CStr;
use std::io;
use std::path::{Path, PathBuf};
use super::*;
pub struct Storage(ffi::blosc2_storage);
impl Default for Storage {
fn default() -> Self {
let storage: ffi::blosc2_storage = unsafe { mem::MaybeUninit::zeroed().assume_init() };
Storage(storage)
}
}
impl Storage {
pub fn set_urlpath<S: AsRef<Path>>(mut self, urlpath: S) -> Result<Self> {
self.0.urlpath = CString::new(urlpath.as_ref().to_string_lossy().to_string())
.map_err(|e| Error::Other(e.to_string()))?
.into_raw();
Ok(self)
}
pub fn get_urlpath(&self) -> Result<Option<&str>> {
if self.0.urlpath.is_null() {
return Ok(None);
}
unsafe {
CStr::from_ptr(self.0.urlpath)
.to_str()
.map(|v| Some(v))
.map_err(|e| Error::Other(e.to_string()))
}
}
pub fn set_contiguous(mut self, contiguous: bool) -> Self {
self.0.contiguous = contiguous;
self
}
pub fn set_cparams(mut self, cparams: &mut CParams) -> Self {
self.0.cparams = &mut cparams.0;
self
}
pub fn get_cparams(&self) -> CParams {
CParams(unsafe { *self.0.cparams })
}
pub fn set_dparams(mut self, dparams: &mut DParams) -> Self {
self.0.dparams = &mut dparams.0;
self
}
}
#[derive(Clone)]
pub struct Chunk {
pub(crate) chunk: Arc<RwLock<*mut u8>>,
pub(crate) needs_free: bool,
}
unsafe impl Sync for Chunk {}
unsafe impl Send for Chunk {}
impl TryFrom<Vec<u8>> for Chunk {
type Error = Error;
#[inline]
fn try_from(v: Vec<u8>) -> Result<Self> {
Self::from_vec(v)
}
}
impl Chunk {
pub fn new(chunk: *mut u8, needs_free: bool) -> Self {
Self {
chunk: Arc::new(RwLock::new(chunk)),
needs_free,
}
}
#[inline]
pub fn from_vec(v: Vec<u8>) -> Result<Self> {
let mut v = v;
v.shrink_to_fit();
if let Err(_) = CompressedBufferInfo::try_from(v.as_slice()) {
return Err("Appears this buffer is not a valid blosc2 chunk".into());
}
let ptr = v.as_mut_ptr();
mem::forget(v);
Ok(Self::new(ptr as _, true))
}
pub fn into_vec(self) -> Result<Vec<u8>> {
let info = self.info()?;
let buf =
unsafe { Vec::from_raw_parts(*self.chunk.write(), info.cbytes(), info.cbytes()) };
if !self.needs_free {
return Ok(buf.clone());
}
Ok(buf)
}
pub fn as_slice(&self) -> Result<&[u8]> {
let info = CompressedBufferInfo::try_from(*self.chunk.read() as *const c_void)?;
let slice = unsafe { std::slice::from_raw_parts(*self.chunk.read(), info.cbytes()) };
Ok(slice)
}
#[inline]
pub fn len<T>(&self) -> Result<usize> {
CompressedBufferInfo::try_from(*self.chunk.read() as *const c_void)
.map(|info| info.nbytes() / mem::size_of::<T>())
}
#[inline]
pub fn getitems<T>(&self, offset: usize, n_items: usize) -> Result<Vec<T>> {
if self.len::<T>()? < offset + n_items {
return Err(format!(
"Would be out of bounds. Chunk contains {} elements",
self.len::<T>()?
)
.into());
}
getitems(self.as_slice()?, offset, n_items)
}
pub fn uninit<T>(cparams: CParams, len: usize) -> Result<Self> {
if mem::size_of::<T>() != cparams.0.typesize as usize {
return Err("typesize mismatch between CParams and T".into());
}
let mut dst = Vec::with_capacity(
(len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
);
let nbytes = unsafe {
ffi::blosc2_chunk_uninit(
cparams.0,
len as i32 * cparams.0.typesize,
dst.as_mut_ptr() as *mut c_void,
dst.capacity() as i32,
)
};
if nbytes < 0 {
return Err("Failed to create uninitialized chunk".into());
}
unsafe { dst.set_len(nbytes as _) };
Self::from_vec(dst)
}
pub fn repeatval<T>(cparams: CParams, value: T, len: usize) -> Result<Self> {
if mem::size_of::<T>() != cparams.0.typesize as usize {
return Err("typesize mismatch between CParams and T".into());
}
let mut dst = Vec::with_capacity(
(len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
);
let nbytes = unsafe {
ffi::blosc2_chunk_repeatval(
cparams.0,
len as i32 * cparams.0.typesize,
dst.as_mut_ptr() as _,
dst.capacity() as _,
&value as *const T as _,
)
};
if nbytes < 0 {
return Err("Failed to create chunk".into());
}
unsafe { dst.set_len(nbytes as _) };
Self::from_vec(dst)
}
pub fn zeros<T>(cparams: CParams, len: usize) -> Result<Self> {
if mem::size_of::<T>() != cparams.0.typesize as usize {
return Err("typesize mismatch between CParams and T".into());
}
let mut dst = Vec::with_capacity(
(len * cparams.0.typesize as usize) + ffi::BLOSC_EXTENDED_HEADER_LENGTH as usize,
);
let nbytes = unsafe {
ffi::blosc2_chunk_zeros(
cparams.0,
len as i32 * cparams.0.typesize,
dst.as_mut_ptr() as _,
dst.capacity() as i32,
)
};
if nbytes < 0 {
return Err(Error::Blosc2(Blosc2Error::from(nbytes)));
}
unsafe { dst.set_len(nbytes as usize) };
Self::from_vec(dst)
}
pub fn compression_ratio(&self) -> Result<f32> {
let info = self.info()?;
if info.cbytes() == 0 {
return Ok(0f32);
}
Ok(info.nbytes() as f32 / info.cbytes() as f32)
}
#[inline]
pub fn compress<T: 'static>(
src: &[T],
typesize: Option<usize>,
clevel: Option<CLevel>,
filter: Option<Filter>,
codec: Option<Codec>,
) -> Result<Self> {
crate::compress(src, typesize, clevel, filter, codec).map(Self::from_vec)?
}
pub fn decompress<T>(&self) -> Result<Vec<T>> {
let slice =
unsafe { std::slice::from_raw_parts(*self.chunk.read(), self.info()?.cbytes) };
crate::decompress(slice)
}
#[inline]
pub fn from_schunk(schunk: &mut SChunk, nchunk: usize) -> Result<Self> {
let mut chunk: *mut u8 = std::ptr::null_mut();
let mut needs_free: bool = false;
let rc = unsafe {
ffi::blosc2_schunk_get_chunk(
*schunk.0.read(),
nchunk as _,
&mut chunk as _,
&mut needs_free,
)
};
if rc < 0 {
return Err(Error::Blosc2(Blosc2Error::from(rc as i32)));
}
Ok(Self {
chunk: Arc::new(RwLock::new(chunk)),
needs_free,
})
}
pub fn nbytes(&self) -> Result<usize> {
self.info().map(|info| info.nbytes)
}
pub fn cbytes(&self) -> Result<usize> {
self.info().map(|info| info.cbytes)
}
#[inline]
pub fn info(&self) -> Result<CompressedBufferInfo> {
let mut nbytes = 0;
let mut cbytes = 0;
let mut blocksize = 0;
let rc = unsafe {
ffi::blosc2_cbuffer_sizes(
*self.chunk.read() as _,
&mut nbytes,
&mut cbytes,
&mut blocksize,
)
};
if rc < 0 {
return Err(Blosc2Error::from(rc).into());
}
Ok(CompressedBufferInfo {
nbytes: nbytes as _,
cbytes: cbytes as _,
blocksize: blocksize as _,
})
}
}
impl Drop for Chunk {
fn drop(&mut self) {
if self.needs_free && Arc::strong_count(&self.chunk) == 1 {
unsafe { blosc2_sys::libc::free(*self.chunk.write() as _) };
}
}
}
#[derive(Clone)]
pub struct SChunk(pub(crate) Arc<RwLock<*mut ffi::blosc2_schunk>>);
impl SChunk {
pub fn new(storage: Storage) -> Self {
let mut storage = storage;
let schunk = unsafe { ffi::blosc2_schunk_new(&mut storage.0) };
Self(Arc::new(RwLock::new(schunk)))
}
pub fn copy(&self) -> Self {
let schunk =
unsafe { ffi::blosc2_schunk_copy(*self.0.read(), (**self.0.read()).storage) };
Self(Arc::new(RwLock::new(schunk)))
}
pub fn frame(&self) -> Result<&[u8]> {
unsafe {
if (**self.0.read()).frame.is_null() {
return Err(Error::from("schunk frame is null"));
}
let len = ffi::blosc2_schunk_frame_len(*self.0.read()) as usize;
let buf = std::slice::from_raw_parts((**self.0.read()).frame as _, len);
Ok(buf)
}
}
#[inline]
pub(crate) fn inner(&self) -> &ffi::blosc2_schunk {
unsafe { &(**self.0.read()) }
}
#[inline]
#[allow(dead_code)]
pub(crate) fn inner_mut(&mut self) -> &mut ffi::blosc2_schunk {
unsafe { &mut (**self.0.write()) }
}
#[inline]
pub fn append_buffer<T>(&mut self, data: &[T]) -> Result<usize> {
if data.is_empty() {
return Ok(self.inner().nchunks as usize);
}
let nbytes = mem::size_of::<T>() * data.len();
let typesize = self.typesize();
if nbytes % self.typesize() != 0 {
let msg = format!("Buffer ({nbytes}) not evenly divisible by typesize: {typesize}");
return Err(Error::Other(msg));
}
let nchunks = unsafe {
ffi::blosc2_schunk_append_buffer(*self.0.read(), data.as_ptr() as _, nbytes as _)
};
if nchunks < 0 {
return Err(Blosc2Error::from(nchunks as i32).into());
}
Ok(nchunks as _)
}
#[inline]
pub fn decompress_chunk<T>(&mut self, nchunk: usize, dst: &mut [T]) -> Result<usize> {
let chunk = Chunk::from_schunk(self, nchunk)?;
let info = chunk.info()?;
if dst.len() < info.nbytes as usize {
let msg = format!(
"Not large enough, need {} but got {}",
info.nbytes,
dst.len()
);
return Err(msg.into());
}
let ptr = dst.as_mut_ptr() as _;
let size = unsafe {
ffi::blosc2_schunk_decompress_chunk(
*self.0.read(),
nchunk as _,
ptr,
info.nbytes as _,
)
};
if size < 0 {
return Err(Blosc2Error::from(size).into());
} else if size == 0 {
let msg = format!("Non-initialized error decompressing chunk '{}'", nchunk);
return Err(msg.into());
} else {
Ok(size as _)
}
}
#[inline]
pub fn decompress_chunk_vec<T>(&mut self, nchunk: usize) -> Result<Vec<T>> {
let chunk = Chunk::from_schunk(self, nchunk)?;
chunk.decompress()
}
pub fn set_slice_buffer(&self, start: usize, stop: usize, buf: &[u8]) -> Result<()> {
if stop > self.len() {
return Err(Error::from(format!(
"`stop`: {} must be less than len: {}",
stop,
self.len(),
)));
}
if buf.len() % self.typesize() != 0 {
return Err(Error::from(
"Buffer is not evenly divisible by SChunk typesize",
));
}
let rc = unsafe {
ffi::blosc2_schunk_set_slice_buffer(
*self.0.write(),
start as _,
stop as _,
buf.as_ptr() as *const _ as *mut _,
)
};
if rc != 0 {
return Err(Blosc2Error::from(rc).into());
}
Ok(())
}
pub fn get_slice_buffer(&self, start: usize, stop: usize) -> Result<Vec<u8>> {
if stop > self.len() {
return Err(Error::from(format!(
"Out of bounds. `stop`={}, is more than length={}",
stop,
self.len()
)));
}
if stop <= start {
return Err(Error::from("start must be less than stop"));
}
let nbytes = (stop - start) * self.typesize();
let mut buf = vec![0u8; nbytes];
let rc = unsafe {
ffi::blosc2_schunk_get_slice_buffer(
*self.0.read(),
start as _,
stop as _,
buf.as_mut_ptr() as _,
)
};
if rc != 0 {
return Err(Blosc2Error::from(rc).into());
}
Ok(buf)
}
pub fn get_slice_buffer_as_type<T>(&self, start: usize, stop: usize) -> Result<Vec<T>> {
if mem::size_of::<T>() != self.typesize() {
return Err(Error::from("Size of T does not match schunk typesize"));
}
let buf = self.get_slice_buffer(start, stop)?;
Ok(unsafe { mem::transmute(buf) })
}
pub fn get_chunk(&self, nchunk: usize) -> Result<Chunk> {
let mut needs_free = true;
let mut chunk = std::ptr::null_mut();
let nbytes = unsafe {
ffi::blosc2_schunk_get_chunk(
*self.0.read(),
nchunk as _,
&mut chunk as _,
&mut needs_free,
)
};
if nbytes < 0 {
Err(Blosc2Error::from(nbytes).into())
} else {
Ok(Chunk::new(chunk, needs_free))
}
}
pub fn into_vec(self) -> Result<Vec<u8>> {
if self.is_empty() {
return Ok(vec![]);
}
unsafe { ffi::blosc2_schunk_avoid_cframe_free(*self.0.read(), true) };
let mut needs_free = true;
let mut ptr: *mut u8 = std::ptr::null_mut();
let len =
unsafe { ffi::blosc2_schunk_to_buffer(*self.0.read(), &mut ptr, &mut needs_free) };
if len < 0 {
return Err(Blosc2Error::from(len as i32).into());
}
let mut buf = unsafe { Vec::from_raw_parts(ptr, len as _, len as _) };
if !needs_free {
buf = buf.clone(); }
Ok(buf)
}
pub fn from_vec(buf: Vec<u8>) -> Result<Self> {
let mut buf = buf;
let schunk =
unsafe { ffi::blosc2_schunk_from_buffer(buf.as_mut_ptr(), buf.len() as _, false) };
if schunk.is_null() {
return Err(Error::from(
"Failed to get schunk from buffer; might not be valid buffer for schunk",
));
}
unsafe { ffi::blosc2_schunk_avoid_cframe_free(schunk, true) };
mem::forget(buf); Ok(Self(Arc::new(RwLock::new(schunk))))
}
pub fn is_contiguous(&self) -> bool {
unsafe { (*(self.inner()).storage).contiguous }
}
pub fn typesize(&self) -> usize {
self.inner().typesize as _
}
pub fn compression_ratio(&self) -> f32 {
if self.inner().cbytes == 0 {
return 0f32;
}
self.inner().nbytes as f32 / self.inner().cbytes as f32
}
pub fn n_chunks(&self) -> usize {
self.inner().nchunks as _
}
pub fn chunk_shape(&self) -> usize {
(self.inner().chunksize / self.inner().typesize) as _
}
pub fn blocksize(&self) -> usize {
self.inner().blocksize as _
}
pub fn nbytes(&self) -> usize {
self.inner().nbytes as _
}
pub fn cbytes(&self) -> usize {
self.inner().cbytes as _
}
pub fn path(&self) -> Option<std::path::PathBuf> {
let urlpath_ptr = unsafe { (*(self.inner().storage)).urlpath };
if urlpath_ptr.is_null() {
return None;
}
let urlpath = unsafe { CStr::from_ptr(urlpath_ptr) };
urlpath.to_str().map(PathBuf::from).ok()
}
pub fn len(&self) -> usize {
(self.inner().nbytes / self.inner().typesize as i64) as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Drop for SChunk {
fn drop(&mut self) {
if Arc::strong_count(&self.0) == 1 && !(*self.0.read()).is_null() {
unsafe { ffi::blosc2_schunk_free(*self.0.write()) };
}
}
}
impl io::Write for SChunk {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.append_buffer(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub struct SChunkDecoder<'schunk> {
pub(crate) schunk: &'schunk mut SChunk,
pub(crate) buf: io::Cursor<Vec<u8>>,
pub(crate) nchunk: usize,
}
impl<'schunk> SChunkDecoder<'schunk> {
pub fn new(schunk: &'schunk mut SChunk) -> Self {
Self {
schunk,
buf: io::Cursor::new(vec![]),
nchunk: 0,
}
}
}
impl<'schunk> io::Read for SChunkDecoder<'schunk> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.buf.position() as usize == self.buf.get_ref().len() {
if self.nchunk >= self.schunk.n_chunks() {
return Ok(0);
}
self.buf.get_mut().truncate(0);
self.buf.set_position(0);
let chunk = Chunk::from_schunk(self.schunk, self.nchunk)?;
let nbytes = chunk.info()?.nbytes();
if nbytes <= buf.len() {
self.schunk.decompress_chunk(self.nchunk, buf)?;
self.nchunk += 1;
return Ok(nbytes);
} else {
self.buf.get_mut().resize(nbytes as _, 0u8);
let nbytes_written: usize = self
.schunk
.decompress_chunk(self.nchunk, self.buf.get_mut().as_mut_slice())?;
debug_assert_eq!(nbytes_written, nbytes);
}
self.nchunk += 1;
}
self.buf.read(buf)
}
}
}
pub struct CParams(ffi::blosc2_cparams);
impl CParams {
pub fn new<T>() -> Self {
Self::default().set_typesize::<T>()
}
pub fn from_typesize(typesize: usize) -> Self {
let mut cparams = Self::default();
cparams.0.typesize = typesize as _;
cparams
}
pub(crate) fn into_inner(self) -> ffi::blosc2_cparams {
self.0
}
#[allow(dead_code)]
pub(crate) fn inner_ref_mut(&mut self) -> &mut ffi::blosc2_cparams {
&mut self.0
}
pub fn set_codec(mut self, codec: Codec) -> Self {
self.0.compcode = codec as _;
self
}
pub fn set_clevel(mut self, clevel: CLevel) -> Self {
self.0.clevel = clevel as _;
self
}
pub fn set_filter(mut self, filter: Filter) -> Self {
self.0.filters[ffi::BLOSC2_MAX_FILTERS as usize - 1] = filter as _;
self
}
pub fn set_nthreads(mut self, n: usize) -> Self {
self.0.nthreads = n as _;
self
}
pub fn get_nthreads(&self) -> usize {
self.0.nthreads as _
}
pub fn set_typesize<T>(mut self) -> Self {
self.0.typesize = mem::size_of::<T>() as _;
self
}
pub fn get_typesize(&self) -> usize {
self.0.typesize as _
}
}
impl Default for CParams {
#[inline]
fn default() -> Self {
let mut cparams: ffi::blosc2_cparams = unsafe { mem::MaybeUninit::zeroed().assume_init() };
cparams.compcode = Codec::default() as _;
cparams.clevel = CLevel::default() as _;
cparams.typesize = 1;
cparams.splitmode = ffi::BLOSC_FORWARD_COMPAT_SPLIT as _;
cparams.filters[ffi::BLOSC2_MAX_FILTERS as usize - 1] = Filter::default() as _;
cparams.nthreads = get_nthreads() as _;
Self(cparams)
}
}
impl<T> From<&T> for CParams {
fn from(_: &T) -> Self {
let mut cparams = CParams::default();
cparams.0.typesize = mem::size_of::<T>() as _;
cparams
}
}
pub struct DParams(pub(crate) ffi::blosc2_dparams);
impl DParams {
pub fn set_nthreads(mut self, n: usize) -> Self {
self.0.nthreads = n as _;
self
}
pub fn get_nthreads(&self) -> usize {
self.0.nthreads as _
}
}
impl Default for DParams {
#[inline]
fn default() -> Self {
let mut dparams: ffi::blosc2_dparams = unsafe { mem::MaybeUninit::zeroed().assume_init() };
dparams.nthreads = get_nthreads() as _;
Self(dparams)
}
}
#[derive(Clone)]
pub struct Context(pub(crate) *mut ffi::blosc2_context);
impl Context {
pub fn get_cparams(&self) -> Result<CParams> {
if self.0.is_null() {
return Err("Context pointer is null".into());
}
let mut cparams = CParams::default();
let rc = unsafe { ffi::blosc2_ctx_get_cparams(self.0, &mut cparams.0) };
if rc < 0 {
return Err(Blosc2Error::from(rc).into());
}
Ok(cparams)
}
pub fn get_dparams(&self) -> Result<DParams> {
if self.0.is_null() {
return Err("Context pointer is null".into());
}
let mut dparams = DParams::default();
let rc = unsafe { ffi::blosc2_ctx_get_dparams(self.0, &mut dparams.0) };
if rc < 0 {
return Err(Blosc2Error::from(rc).into());
}
Ok(dparams)
}
}
impl From<DParams> for Context {
fn from(dparams: DParams) -> Self {
Self(unsafe { ffi::blosc2_create_dctx(dparams.0) })
}
}
impl From<CParams> for Context {
fn from(cparams: CParams) -> Self {
Self(unsafe { ffi::blosc2_create_cctx(cparams.0) })
}
}
impl Default for Context {
fn default() -> Self {
let ctx = unsafe { ffi::blosc2_create_cctx(CParams::default().into_inner()) };
Self(ctx)
}
}
impl Drop for Context {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { ffi::blosc2_free_ctx(self.0) };
}
}
}
pub struct CompressedBufferInfo {
nbytes: usize,
cbytes: usize,
blocksize: usize,
}
impl CompressedBufferInfo {
pub fn nbytes(&self) -> usize {
self.nbytes
}
pub fn cbytes(&self) -> usize {
self.cbytes
}
pub fn blocksize(&self) -> usize {
self.blocksize
}
}
impl<T> TryFrom<&[T]> for CompressedBufferInfo {
type Error = Error;
#[inline]
fn try_from(buf: &[T]) -> Result<Self> {
let mut nbytes = 0i32;
let mut cbytes = 0i32;
let mut blocksize = 0i32;
let code = unsafe {
ffi::blosc2_cbuffer_sizes(
buf.as_ptr() as *const c_void,
&mut nbytes as *mut _,
&mut cbytes as *mut _,
&mut blocksize as *mut _,
)
};
if code < 0 {
return Err(Blosc2Error::from(code).into());
}
Ok(CompressedBufferInfo {
nbytes: nbytes as _,
cbytes: cbytes as _,
blocksize: blocksize as _,
})
}
}
impl TryFrom<*const c_void> for CompressedBufferInfo {
type Error = Error;
#[inline]
fn try_from(ptr: *const c_void) -> Result<Self> {
let mut nbytes = 0i32;
let mut cbytes = 0i32;
let mut blocksize = 0i32;
let code = unsafe {
ffi::blosc2_cbuffer_sizes(
ptr,
&mut nbytes as *mut _,
&mut cbytes as *mut _,
&mut blocksize as *mut _,
)
};
if code < 0 {
return Err(Blosc2Error::from(code).into());
}
Ok(CompressedBufferInfo {
nbytes: nbytes as _,
cbytes: cbytes as _,
blocksize: blocksize as _,
})
}
}
#[inline]
pub fn getitems<T>(src: &[u8], offset: usize, n_items: usize) -> Result<Vec<T>> {
let mut dst = Vec::with_capacity(n_items);
let nbytes = unsafe {
ffi::blosc2_getitem(
src.as_ptr() as *const c_void,
src.len() as _,
offset as _,
n_items as _,
dst.as_mut_ptr() as *mut c_void,
(n_items * mem::size_of::<T>()) as i32,
)
};
if nbytes < 0 {
return Err(Blosc2Error::from(nbytes).into());
}
unsafe { dst.set_len(nbytes as usize / mem::size_of::<T>()) };
Ok(dst)
}
#[inline]
pub fn compress_ctx<T>(src: &[T], ctx: &mut Context) -> Result<Vec<u8>> {
if src.is_empty() {
return Ok(vec![]);
}
let mut dst =
vec![0u8; max_compress_len(src, ctx.get_cparams().ok().map(|c| c.get_typesize()))];
let size = compress_into_ctx(src, &mut dst, ctx)?;
if dst.len() > size {
dst.truncate(size as _);
}
Ok(dst)
}
#[inline]
pub fn compress_into_ctx<T>(src: &[T], dst: &mut [u8], ctx: &mut Context) -> Result<usize> {
if src.is_empty() {
return Ok(0);
}
let size = unsafe {
ffi::blosc2_compress_ctx(
ctx.0,
src.as_ptr() as *const c_void,
(src.len() * ctx.get_cparams()?.get_typesize()) as _,
dst.as_mut_ptr() as *mut c_void,
dst.len() as _,
)
};
if size == 0 {
return Err(format!("Buffer is incompressible").into());
} else if size < 0 {
return Err(Blosc2Error::from(size).into());
}
Ok(size as _)
}
#[inline(always)]
pub fn max_compress_len<T>(src: &[T], typesize: Option<usize>) -> usize {
(src.len() * typesize.unwrap_or_else(|| mem::size_of::<T>()))
+ ffi::BLOSC2_MAX_OVERHEAD as usize
}
pub fn max_compress_len_bytes(len_bytes: usize) -> usize {
len_bytes + ffi::BLOSC2_MAX_OVERHEAD as usize
}
#[inline]
pub fn compress<T: 'static>(
src: &[T],
typesize: Option<usize>,
clevel: Option<CLevel>,
filter: Option<Filter>,
codec: Option<Codec>,
) -> Result<Vec<u8>> {
if src.is_empty() {
return Ok(vec![]);
}
let mut dst = Vec::with_capacity(max_compress_len(src, typesize));
let typesize = typesize.unwrap_or_else(|| mem::size_of::<T>());
set_compressor(codec.unwrap_or_default())?;
let multiplier = (&src[0] as &dyn std::any::Any)
.downcast_ref::<u8>()
.map(|_| 1)
.unwrap_or(typesize);
let n_bytes = unsafe {
ffi::blosc2_compress(
clevel.unwrap_or_default() as _,
filter.unwrap_or_default() as _,
typesize as _,
src.as_ptr() as *const c_void,
(src.len() * multiplier) as _,
dst.as_mut_ptr() as *mut c_void,
dst.capacity() as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
} else if n_bytes == 0 {
return Err("Data is not compressable.".into());
}
unsafe { dst.set_len(n_bytes as _) };
Ok(dst)
}
#[inline]
pub fn compress_into<T: 'static>(
src: &[T],
dst: &mut [u8],
typesize: Option<usize>,
clevel: Option<CLevel>,
filter: Option<Filter>,
codec: Option<Codec>,
) -> Result<usize> {
if src.is_empty() {
return Ok(0);
}
let typesize = typesize.unwrap_or_else(|| mem::size_of::<T>());
set_compressor(codec.unwrap_or_default())?;
let multiplier = (&src[0] as &dyn std::any::Any)
.downcast_ref::<u8>()
.map(|_| 1)
.unwrap_or(typesize);
let n_bytes = unsafe {
ffi::blosc2_compress(
clevel.unwrap_or_default() as _,
filter.unwrap_or_default() as _,
typesize as _,
src.as_ptr() as *const c_void,
(src.len() * multiplier) as _,
dst.as_mut_ptr() as *mut c_void,
dst.len() as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
} else if n_bytes == 0 {
return Err("Data is not compressable.".into());
}
Ok(n_bytes as _)
}
#[inline]
pub fn decompress_ctx<T>(src: &[u8], ctx: &mut Context) -> Result<Vec<T>> {
if src.is_empty() {
return Ok(vec![]);
}
let info = CompressedBufferInfo::try_from(src)?;
let n_elements = info.nbytes as usize / mem::size_of::<T>();
let mut dst = Vec::with_capacity(n_elements);
let n_bytes = unsafe {
ffi::blosc2_decompress_ctx(
ctx.0,
src.as_ptr() as *const c_void,
src.len() as i32,
dst.as_mut_ptr() as *mut c_void,
info.nbytes as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
}
debug_assert_eq!(n_bytes as usize, info.nbytes);
unsafe { dst.set_len(n_elements) };
Ok(dst)
}
#[inline]
pub fn decompress_into_ctx<T>(src: &[T], dst: &mut [T], ctx: &mut Context) -> Result<usize> {
if src.is_empty() {
return Ok(0);
}
let info = CompressedBufferInfo::try_from(src)?;
let n_bytes = unsafe {
ffi::blosc2_decompress_ctx(
ctx.0,
src.as_ptr() as *const c_void,
src.len() as i32,
dst.as_mut_ptr() as *mut c_void,
info.nbytes as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
}
debug_assert_eq!(n_bytes as usize, info.nbytes);
Ok(n_bytes as _)
}
#[inline]
pub fn len<T>(src: &[u8]) -> Result<usize> {
let info = CompressedBufferInfo::try_from(src)?;
Ok(info.nbytes() / mem::size_of::<T>())
}
#[inline]
pub fn decompress<T>(src: &[u8]) -> Result<Vec<T>> {
if src.is_empty() {
return Ok(vec![]);
}
let info = CompressedBufferInfo::try_from(src)?;
let n_elements = info.nbytes as usize / mem::size_of::<T>();
let mut dst = Vec::with_capacity(n_elements);
let n_bytes = unsafe {
ffi::blosc2_decompress(
src.as_ptr() as *const c_void,
src.len() as i32,
dst.as_mut_ptr() as *mut c_void,
info.nbytes as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
}
debug_assert_eq!(n_bytes as usize, info.nbytes);
unsafe { dst.set_len(n_elements) };
Ok(dst)
}
#[inline]
pub fn decompress_into<T>(src: &[u8], dst: &mut [T]) -> Result<usize> {
if src.is_empty() {
return Ok(0);
}
let info = CompressedBufferInfo::try_from(src)?;
let n_bytes = unsafe {
ffi::blosc2_decompress(
src.as_ptr() as *const c_void,
src.len() as i32,
dst.as_mut_ptr() as *mut c_void,
info.nbytes as _,
)
};
if n_bytes < 0 {
return Err(Blosc2Error::from(n_bytes).into());
}
debug_assert_eq!(n_bytes as usize, info.nbytes);
Ok(n_bytes as _)
}
#[inline]
pub fn set_compressor(codec: Codec) -> Result<()> {
let codec_name = codec.to_name_cstring()?;
let rc = unsafe { ffi::blosc1_set_compressor(codec_name.as_ptr()) };
if rc < 0 {
Err(Blosc2Error::from(rc).into())
} else {
Ok(())
}
}
pub fn set_nthreads(nthreads: usize) -> usize {
let n = unsafe { ffi::blosc2_set_nthreads(nthreads as _) };
n as _
}
pub fn get_nthreads() -> usize {
unsafe { ffi::blosc2_get_nthreads() as _ }
}
pub fn get_complib_info(codec: Codec) -> Result<String> {
let mut compname = std::ptr::null();
let mut complib = std::ptr::null_mut();
let mut version = std::ptr::null_mut();
if unsafe { ffi::blosc2_compcode_to_compname(codec as i32, &mut compname) } == -1 {
return Err("Codec not recognized".into());
}
if unsafe { ffi::blosc2_get_complib_info(compname as _, &mut complib, &mut version) } == -1 {
return Err("Codec not supported".into());
}
let info = unsafe {
format!(
"{}: {}",
CString::from_raw(complib)
.into_string()
.map_err(|e| e.to_string())?,
CString::from_raw(version)
.into_string()
.map_err(|e| e.to_string())?
)
};
Ok(info)
}
pub fn get_version_string() -> Result<String> {
CStr::from_bytes_with_nul(ffi::BLOSC2_VERSION_STRING)
.map_err(|e| e.to_string())?
.to_str()
.map_err(|e| e.to_string().into())
.map(|s| s.to_string())
}
pub fn init() {
unsafe { ffi::blosc2_init() }
}
pub fn destroy() {
unsafe { ffi::blosc2_destroy() }
}
#[cfg(test)]
mod tests {
use ctor::{ctor, dtor};
use std::io::Cursor;
use super::*;
#[ctor]
fn blosc2_init() {
init();
}
#[dtor]
fn blosc2_destory() {
destroy();
}
#[test]
fn test_compress_ctx() -> Result<()> {
let input = b"some data";
let compressed = compress_ctx(input, &mut Context::from(CParams::from(&input[0])))?;
let decompressed = decompress(&compressed)?;
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[test]
fn test_compress_into_ctx() -> Result<()> {
let input = b"some data";
let mut compressed = vec![0u8; 100];
let n_bytes = compress_into_ctx(
input,
&mut compressed,
&mut Context::from(CParams::from(&input[0])),
)?;
let decompressed = decompress(&compressed[..n_bytes])?;
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[test]
fn test_decompress_ctx() -> Result<()> {
let input = b"some data";
let compressed = compress(input, None, None, None, None)?;
let decompressed = decompress_ctx(&compressed, &mut Context::from(DParams::default()))?;
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[test]
fn test_decompress_into_ctx() -> Result<()> {
let input = b"some data";
let compressed = compress(input, None, None, None, None)?;
let mut decompressed = vec![0u8; input.len()];
let n_bytes = decompress_into_ctx(
&compressed,
&mut decompressed,
&mut Context::from(DParams::default()),
)?;
assert_eq!(n_bytes, input.len());
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[test]
fn test_basic_roundtrip() -> Result<()> {
let input = b"some data";
let compressed = compress(input, None, None, None, None)?;
let decompressed = decompress(&compressed)?;
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[test]
fn test_basic_roundtrip_into() -> Result<()> {
let input = b"some data";
let mut compressed = vec![0u8; 100];
let n_bytes = compress_into(input, &mut compressed, None, None, None, None)?;
let mut decompressed = vec![0u8; input.len()];
let n_out = decompress_into(&compressed[..n_bytes], &mut decompressed)?;
assert_eq!(n_out, input.len());
assert_eq!(input, decompressed.as_slice());
Ok(())
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_schunk_basic() -> Result<()> {
let input = b"some data";
let storage = schunk::Storage::default()
.set_contiguous(true)
.set_cparams(&mut CParams::from(&input[0]))
.set_dparams(&mut DParams::default());
let mut schunk = schunk::SChunk::new(storage);
assert!(schunk.is_contiguous());
assert_eq!(schunk.typesize(), 1);
assert!(schunk.path().is_none());
let mut decompressed = vec![0u8; input.len()];
let n = schunk.append_buffer(input)?;
schunk.decompress_chunk(n - 1, &mut decompressed)?;
assert_eq!(input, decompressed.as_slice());
{
let _cloned = schunk.clone();
}
assert_eq!(schunk.n_chunks(), 1);
let v = schunk.into_vec()?;
schunk = schunk::SChunk::from_vec(v)?;
assert_eq!(schunk.n_chunks(), 1);
Ok(())
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_schunk_write() -> Result<()> {
let input = std::iter::repeat(b"some data")
.take(BUFSIZE)
.flat_map(|v| v.to_vec())
.collect::<Vec<u8>>();
let storage = schunk::Storage::default()
.set_contiguous(true)
.set_cparams(&mut CParams::from(&input[0]))
.set_dparams(&mut DParams::default());
let mut schunk = schunk::SChunk::new(storage);
let nbytes = std::io::copy(&mut Cursor::new(input.clone()), &mut schunk)
.map_err(|e| Error::Other(e.to_string()))?;
assert_eq!(nbytes as usize, input.len());
let ratio = schunk.compression_ratio();
assert!(84. < ratio);
assert!(86. > ratio);
let mut uncompressed = vec![];
let mut decoder = schunk::SChunkDecoder::new(&mut schunk);
let n = std::io::copy(&mut decoder, &mut uncompressed).unwrap();
assert_eq!(input, uncompressed.as_slice());
assert_eq!(n as usize, input.len());
Ok(())
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_get_version_string() -> Result<()> {
let version = get_version_string()?;
assert_eq!(&version, "2.14.3");
Ok(())
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_get_complib_version_string() -> Result<()> {
let info = get_complib_info(Codec::BloscLz)?;
assert_eq!(&info, "BloscLZ: 2.5.3");
Ok(())
}
}