hdf5 0.8.1

Thread-safe Rust bindings for the HDF5 library.
use std::ptr;
use std::slice;

use lazy_static::lazy_static;

use hdf5_sys::h5p::{H5Pget_chunk, H5Pget_filter_by_id2, H5Pmodify_filter};
use hdf5_sys::h5t::{H5Tclose, H5Tget_class, H5Tget_size, H5Tget_super, H5T_ARRAY};
use hdf5_sys::h5z::{H5Z_class2_t, H5Z_filter_t, H5Zregister, H5Z_CLASS_T_VERS, H5Z_FLAG_REVERSE};

use crate::globals::{H5E_CALLBACK, H5E_PLIST};
use crate::internal_prelude::*;

pub use blosc_sys::{
    BLOSC_BITSHUFFLE, BLOSC_BLOSCLZ, BLOSC_LZ4, BLOSC_LZ4HC, BLOSC_MAX_TYPESIZE, BLOSC_NOSHUFFLE,
    BLOSC_SHUFFLE, BLOSC_SNAPPY, BLOSC_VERSION_FORMAT, BLOSC_ZLIB, BLOSC_ZSTD,
};

pub use blosc_sys::{
    blosc_cbuffer_sizes, blosc_compcode_to_compname, blosc_compress, blosc_decompress,
    blosc_get_nthreads, blosc_get_version_string, blosc_init, blosc_list_compressors,
    blosc_set_compressor, blosc_set_nthreads,
};

const BLOSC_FILTER_NAME: &[u8] = b"blosc\0";
pub const BLOSC_FILTER_ID: H5Z_filter_t = 32001;
const BLOSC_FILTER_VERSION: c_uint = 2;

const BLOSC_FILTER_INFO: H5Z_class2_t = H5Z_class2_t {
    version: H5Z_CLASS_T_VERS as _,
    id: BLOSC_FILTER_ID,
    encoder_present: 1,
    decoder_present: 1,
    name: BLOSC_FILTER_NAME.as_ptr().cast(),
    can_apply: None,
    set_local: Some(set_local_blosc),
    filter: Some(filter_blosc),
};

lazy_static! {
    static ref BLOSC_INIT: Result<(), &'static str> = {
        unsafe {
            blosc_init();
        }
        let ret = unsafe { H5Zregister((&BLOSC_FILTER_INFO as *const H5Z_class2_t).cast()) };
        if H5ErrorCode::is_err_code(ret) {
            return Err("Can't register Blosc filter");
        }
        Ok(())
    };
}

pub fn register_blosc() -> Result<(), &'static str> {
    (*BLOSC_INIT).clone()
}

extern "C" fn set_local_blosc(dcpl_id: hid_t, type_id: hid_t, _space_id: hid_t) -> herr_t {
    const MAX_NDIMS: usize = 32;
    let mut flags: c_uint = 0;
    let mut nelmts: size_t = 8;
    let mut values: Vec<c_uint> = vec![0; 8];
    let ret = unsafe {
        H5Pget_filter_by_id2(
            dcpl_id,
            BLOSC_FILTER_ID,
            &mut flags as *mut _,
            &mut nelmts as *mut _,
            values.as_mut_ptr(),
            0,
            ptr::null_mut(),
            ptr::null_mut(),
        )
    };
    if ret < 0 {
        return -1;
    }
    nelmts = nelmts.max(4);
    values[0] = BLOSC_FILTER_VERSION;
    values[1] = BLOSC_VERSION_FORMAT;
    let mut chunkdims: Vec<hsize_t> = vec![0; MAX_NDIMS];
    let ndims: c_int = unsafe { H5Pget_chunk(dcpl_id, MAX_NDIMS as _, chunkdims.as_mut_ptr()) };
    if ndims < 0 {
        return -1;
    }
    if ndims > MAX_NDIMS as _ {
        h5err!("Chunk rank exceeds limit", H5E_PLIST, H5E_CALLBACK);
        return -1;
    }
    let typesize: size_t = unsafe { H5Tget_size(type_id) };
    if typesize == 0 {
        return -1;
    }
    let mut basetypesize = typesize;
    unsafe {
        if H5Tget_class(type_id) == H5T_ARRAY {
            let super_type = H5Tget_super(type_id);
            basetypesize = H5Tget_size(super_type);
            H5Tclose(super_type);
        }
    }
    if basetypesize > BLOSC_MAX_TYPESIZE as _ {
        basetypesize = 1;
    }
    values[2] = basetypesize as _;
    let mut bufsize = typesize;
    for &chunkdim in chunkdims[..ndims as usize].iter() {
        bufsize *= chunkdim as size_t;
    }
    values[3] = bufsize as _;
    let r = unsafe { H5Pmodify_filter(dcpl_id, BLOSC_FILTER_ID, flags, nelmts, values.as_ptr()) };
    if r < 0 {
        -1
    } else {
        1
    }
}

struct BloscConfig {
    pub typesize: size_t,
    pub outbuf_size: size_t,
    pub clevel: c_int,
    pub doshuffle: c_int,
    pub compname: *const c_char,
}

impl Default for BloscConfig {
    fn default() -> Self {
        const DEFAULT_COMPNAME: &[u8] = b"blosclz\0";
        Self {
            typesize: 0,
            outbuf_size: 0,
            clevel: 5,
            doshuffle: 1,
            compname: DEFAULT_COMPNAME.as_ptr().cast(),
        }
    }
}

fn parse_blosc_cdata(cd_nelmts: size_t, cd_values: *const c_uint) -> Option<BloscConfig> {
    let cdata = unsafe { slice::from_raw_parts(cd_values, cd_nelmts as _) };
    let mut cfg = BloscConfig {
        typesize: cdata[2] as _,
        outbuf_size: cdata[3] as _,
        ..BloscConfig::default()
    };
    if cdata.len() >= 5 {
        cfg.clevel = cdata[4] as _;
    };
    if cdata.len() >= 6 {
        let v = unsafe { slice::from_raw_parts(blosc_get_version_string() as *mut u8, 4) };
        if v[0] <= b'1' && v[1] == b'.' && v[2] < b'8' && v[3] == b'.' {
            h5err!(
                "This Blosc library version is not supported. Please update to >= 1.8",
                H5E_PLIST,
                H5E_CALLBACK
            );
            return None;
        }
        cfg.doshuffle = cdata[5] as _;
    }
    if cdata.len() >= 7 {
        let r = unsafe { blosc_compcode_to_compname(cdata[6] as _, &mut cfg.compname as *mut _) };
        if r == -1 {
            let complist = string_from_cstr(unsafe { blosc_list_compressors() });
            let errmsg = format!(
                concat!(
                    "This Blosc library does not have support for the '{}' compressor, ",
                    "but only for: {}"
                ),
                string_from_cstr(cfg.compname),
                complist
            );
            h5err!(errmsg, H5E_PLIST, H5E_CALLBACK);
            return None;
        }
    }
    Some(cfg)
}

extern "C" fn filter_blosc(
    flags: c_uint, cd_nelmts: size_t, cd_values: *const c_uint, nbytes: size_t,
    buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
    let cfg = if let Some(cfg) = parse_blosc_cdata(cd_nelmts, cd_values) {
        cfg
    } else {
        return 0;
    };
    if flags & H5Z_FLAG_REVERSE == 0 {
        unsafe { filter_blosc_compress(&cfg, nbytes, buf_size, buf) }
    } else {
        unsafe { filter_blosc_decompress(&cfg, buf_size, buf) }
    }
}

unsafe fn filter_blosc_compress(
    cfg: &BloscConfig, nbytes: size_t, buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
    let outbuf_size = *buf_size;
    let outbuf = libc::malloc(outbuf_size);
    if outbuf.is_null() {
        h5err!("Can't allocate compression buffer", H5E_PLIST, H5E_CALLBACK);
        return 0;
    }
    blosc_set_compressor(cfg.compname);
    let status =
        blosc_compress(cfg.clevel, cfg.doshuffle, cfg.typesize, nbytes, *buf, outbuf, nbytes);
    if status > 0 {
        libc::free(*buf);
        *buf = outbuf;
        status as _
    } else {
        libc::free(outbuf);
        0
    }
}

unsafe fn filter_blosc_decompress(
    cfg: &BloscConfig, buf_size: *mut size_t, buf: *mut *mut c_void,
) -> size_t {
    let mut outbuf_size: size_t = cfg.outbuf_size;
    let (mut cbytes, mut blocksize): (size_t, size_t) = (0, 0);
    blosc_cbuffer_sizes(
        *buf,
        &mut outbuf_size as *mut _,
        &mut cbytes as *mut _,
        &mut blocksize as *mut _,
    );
    let outbuf = libc::malloc(outbuf_size);
    if outbuf.is_null() {
        h5err!("Can't allocate decompression buffer", H5E_PLIST, H5E_CALLBACK);
        return 0;
    }
    let status = blosc_decompress(*buf, outbuf, outbuf_size);
    if status > 0 {
        libc::free(*buf);
        *buf = outbuf;
        *buf_size = outbuf_size as _;
        status as _
    } else {
        libc::free(outbuf);
        h5err!("Blosc decompression error", H5E_PLIST, H5E_CALLBACK);
        0
    }
}