#[cfg(any(
feature = "szip-pure",
feature = "zstd-pure",
feature = "lz4",
feature = "sz3",
feature = "threads",
))]
mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Linkage {
Ffi,
PureRust,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct BackendVersion {
pub name: &'static str,
pub linkage: Linkage,
pub version: Option<String>,
}
impl BackendVersion {
pub fn ffi(name: &'static str, version: Option<String>) -> Self {
Self {
name,
linkage: Linkage::Ffi,
version,
}
}
pub fn pure_rust(name: &'static str, version: Option<String>) -> Self {
Self {
name,
linkage: Linkage::PureRust,
version,
}
}
}
#[cfg(any(
feature = "szip-pure",
feature = "zstd-pure",
feature = "lz4",
feature = "sz3",
feature = "threads",
))]
fn dep_version(crate_name: &str) -> Option<String> {
built_info::DEPENDENCIES
.iter()
.find(|(name, _)| *name == crate_name)
.map(|(_, ver)| ver.to_string())
}
pub unsafe fn cstr_ptr_to_owned(ptr: *const std::ffi::c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
if s.trim().is_empty() { None } else { Some(s) }
}
#[cfg(feature = "szip")]
unsafe extern "C" {
fn tensogram_libaec_version() -> *const std::ffi::c_char;
}
#[cfg(feature = "szip")]
pub fn szip_ffi_version() -> BackendVersion {
let version = unsafe { cstr_ptr_to_owned(tensogram_libaec_version()) };
BackendVersion::ffi("libaec", version)
}
#[cfg(feature = "szip-pure")]
pub fn szip_pure_version() -> BackendVersion {
BackendVersion::pure_rust("tensogram-szip", dep_version("tensogram-szip"))
}
#[cfg(feature = "zstd")]
unsafe extern "C" {
fn ZSTD_versionString() -> *const std::ffi::c_char;
}
#[cfg(feature = "zstd")]
pub fn zstd_ffi_version() -> BackendVersion {
let version = unsafe { cstr_ptr_to_owned(ZSTD_versionString()) };
BackendVersion::ffi("libzstd", version)
}
#[cfg(feature = "zstd-pure")]
pub fn zstd_pure_version() -> BackendVersion {
BackendVersion::pure_rust("ruzstd", dep_version("ruzstd"))
}
#[cfg(feature = "lz4")]
pub fn lz4_version() -> BackendVersion {
BackendVersion::pure_rust("lz4_flex", dep_version("lz4_flex"))
}
#[cfg(feature = "blosc2")]
unsafe extern "C" {
fn blosc2_get_version_string() -> *const std::ffi::c_char;
}
#[cfg(feature = "blosc2")]
pub fn blosc2_version() -> BackendVersion {
let version = unsafe { cstr_ptr_to_owned(blosc2_get_version_string()) };
BackendVersion::ffi("libblosc2", version)
}
#[cfg(feature = "zfp")]
unsafe extern "C" {
static zfp_version_string: *const std::ffi::c_char;
}
#[cfg(feature = "zfp")]
pub fn zfp_version() -> BackendVersion {
let version = unsafe { cstr_ptr_to_owned(zfp_version_string) };
BackendVersion::ffi("libzfp", version)
}
#[cfg(feature = "sz3")]
pub fn sz3_version() -> BackendVersion {
BackendVersion::ffi("SZ3", dep_version("tensogram-sz3"))
}
#[cfg(feature = "threads")]
pub fn rayon_version() -> BackendVersion {
BackendVersion::pure_rust("rayon", dep_version("rayon"))
}
#[cfg(test)]
mod tests {
use super::*;
fn has_digit(s: &str) -> bool {
s.chars().any(|c| c.is_ascii_digit())
}
fn cstr_buf(s: &str) -> Vec<std::ffi::c_char> {
let mut buf: Vec<std::ffi::c_char> = s.bytes().map(|b| b as std::ffi::c_char).collect();
buf.push(0); buf
}
#[test]
fn cstr_ptr_to_owned_returns_none_for_null() {
let result = unsafe { cstr_ptr_to_owned(std::ptr::null()) };
assert_eq!(result, None);
}
#[test]
fn cstr_ptr_to_owned_returns_none_for_empty_string() {
let buf = cstr_buf("");
let result = unsafe { cstr_ptr_to_owned(buf.as_ptr()) };
assert_eq!(
result, None,
"empty C string should produce None, got {result:?}"
);
}
#[test]
fn cstr_ptr_to_owned_returns_none_for_whitespace_only() {
let buf = cstr_buf(" \t\n ");
let result = unsafe { cstr_ptr_to_owned(buf.as_ptr()) };
assert_eq!(
result, None,
"all-whitespace C string should produce None, got {result:?}"
);
}
#[test]
fn cstr_ptr_to_owned_preserves_normal_string() {
let buf = cstr_buf("1.2.3");
let result = unsafe { cstr_ptr_to_owned(buf.as_ptr()) };
assert_eq!(result.as_deref(), Some("1.2.3"));
}
#[test]
fn cstr_ptr_to_owned_keeps_internal_whitespace() {
let buf = cstr_buf("4.10.0 of Apr 3 2024");
let result = unsafe { cstr_ptr_to_owned(buf.as_ptr()) };
assert_eq!(result.as_deref(), Some("4.10.0 of Apr 3 2024"));
}
#[test]
#[cfg(feature = "szip")]
fn szip_ffi_version_non_empty() {
let v = szip_ffi_version();
let ver = v.version.expect("libaec version should be present");
assert!(!ver.is_empty(), "libaec version string is empty");
assert!(has_digit(&ver), "libaec version has no digit: {ver}");
}
#[test]
#[cfg(feature = "zstd")]
fn zstd_ffi_version_non_empty() {
let v = zstd_ffi_version();
let ver = v.version.expect("libzstd version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "zstd version has no digit: {ver}");
}
#[test]
#[cfg(feature = "lz4")]
fn lz4_version_non_empty() {
let v = lz4_version();
let ver = v.version.expect("lz4_flex version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "lz4 version has no digit: {ver}");
}
#[test]
#[cfg(feature = "blosc2")]
fn blosc2_version_non_empty() {
let v = blosc2_version();
let ver = v.version.expect("libblosc2 version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "blosc2 version has no digit: {ver}");
}
#[test]
#[cfg(feature = "zfp")]
fn zfp_version_non_empty() {
let v = zfp_version();
let ver = v.version.expect("libzfp version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "zfp version has no digit: {ver}");
}
#[test]
#[cfg(feature = "sz3")]
fn sz3_version_non_empty() {
let v = sz3_version();
let ver = v.version.expect("sz3 version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "sz3 version has no digit: {ver}");
}
#[test]
#[cfg(feature = "threads")]
fn rayon_version_non_empty() {
let v = rayon_version();
let ver = v.version.expect("rayon version should be present");
assert!(!ver.is_empty());
assert!(has_digit(&ver), "rayon version has no digit: {ver}");
}
}