fix: correct libaec FFI to use aec_stream struct (fixes SIGSEGV)

The previous aec_buffer_decode declaration used flat parameters which
don't match the actual libaec C API; this caused a SIGSEGV at runtime.
Replace with the correct aec_stream struct (mirroring <libaec.h>) and
update filters_szip.rs to populate and pass &mut AecStream.
Also add empty-input guard in szip_decode_impl and fallback library
path search in build.rs for distros that omit the .pc file.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-30 11:41:41 +00:00
co-authored by Claude Sonnet 4.6
parent d6c4d4f111
commit e91f7fc539
3 changed files with 91 additions and 29 deletions
+19 -14
View File
@@ -48,9 +48,14 @@ fn szip_decode_impl(data: &[u8], cd: &[u32], chunk_size: usize) -> Result<Vec<u8
"szip: unknown output size".into(), "szip: unknown output size".into(),
)); ));
} }
if data.is_empty() {
return Err(FormatError::ChunkedReadError(
"szip: empty input".into(),
));
}
// Map HDF5 options mask to libaec flags. // Map HDF5 options mask to libaec flags.
// bit 2 (0x04): NN (nearest-neighbor) preprocessing // bit 2 (0x04): NN (nearest-neighbor) preprocessing
// bit 5 (0x20): EC (entropy coding) — handled internally by libaec
// bit 6 (0x40): LSB order; absence means MSB // bit 6 (0x40): LSB order; absence means MSB
// bit 8 (0x100): allow k=13 // bit 8 (0x100): allow k=13
let mut flags: u32 = 0; let mut flags: u32 = 0;
@@ -64,25 +69,25 @@ fn szip_decode_impl(data: &[u8], cd: &[u32], chunk_size: usize) -> Result<Vec<u8
flags |= libaec_sys::AEC_ALLOW_K13; flags |= libaec_sys::AEC_ALLOW_K13;
} }
let mut out_len: usize = chunk_size;
let mut out = vec![0u8; chunk_size]; let mut out = vec![0u8; chunk_size];
let result = unsafe { let mut strm = libaec_sys::AecStream::zeroed();
libaec_sys::aec_buffer_decode( strm.next_in = data.as_ptr();
data.as_ptr(), strm.avail_in = data.len();
data.len(), strm.next_out = out.as_mut_ptr();
out.as_mut_ptr(), strm.avail_out = chunk_size;
&mut out_len, strm.bits_per_sample = bits_per_sample;
bits_per_sample, strm.block_size = pixels_per_block;
pixels_per_block, strm.rsi = 128; // HDF5 default: 128 blocks per reference sample interval
flags, strm.flags = flags;
)
}; let result = unsafe { libaec_sys::aec_buffer_decode(&mut strm) };
if result != 0 { if result != 0 {
return Err(FormatError::DecompressionError(format!( return Err(FormatError::DecompressionError(format!(
"szip: libaec error {result}" "szip: libaec error {result}"
))); )));
} }
out.truncate(out_len); let decoded_len = chunk_size - strm.avail_out;
out.truncate(decoded_len);
Ok(out) Ok(out)
} }
+18 -3
View File
@@ -6,7 +6,22 @@ fn main() {
{ {
return; // pkg-config found libaec and emitted the link directives return; // pkg-config found libaec and emitted the link directives
} }
// libaec not found via pkg-config. Do not emit a link directive; the // Fallback: look for libaec.so / libaec.a in standard library paths.
// szip feature in clawhdf5-format gates all actual FFI calls, so the // libaec-dev on Debian/Ubuntu installs the library but omits the .pc file.
// crate compiles and passes tests without libaec installed. let lib_dirs = [
"/usr/lib/x86_64-linux-gnu",
"/usr/lib",
"/usr/local/lib",
"/usr/local/lib/x86_64-linux-gnu",
];
for dir in &lib_dirs {
let so = std::path::Path::new(dir).join("libaec.so");
let a = std::path::Path::new(dir).join("libaec.a");
if so.exists() || a.exists() {
println!("cargo:rustc-link-search=native={dir}");
println!("cargo:rustc-link-lib=aec");
return;
}
}
// libaec not found — szip feature will be unavailable but crate still compiles.
} }
+54 -12
View File
@@ -1,6 +1,9 @@
//! Raw FFI bindings to libaec (Adaptive Entropy Coding library). //! Raw FFI bindings to libaec (Adaptive Entropy Coding library).
//! //!
//! Provides the `aec_buffer_decode` convenience function for one-shot decompression. //! Exposes the `aec_buffer_decode` one-shot convenience function via
//! the `AecStream` control structure, matching the libaec C API.
use std::os::raw::c_void;
// AEC flag constants matching aec.h // AEC flag constants matching aec.h
pub const AEC_DATA_PREPROCESS: u32 = 1; // NN preprocessing pub const AEC_DATA_PREPROCESS: u32 = 1; // NN preprocessing
@@ -8,20 +11,51 @@ pub const AEC_DATA_MSB: u32 = 2; // big-endian sample order
pub const AEC_RESTRICTED: u32 = 4; // restricted coding set pub const AEC_RESTRICTED: u32 = 4; // restricted coding set
pub const AEC_ALLOW_K13: u32 = 8; // allow k=13 option pub const AEC_ALLOW_K13: u32 = 8; // allow k=13 option
/// Mirror of `struct aec_stream` from `<libaec.h>`.
///
/// Must match the C layout exactly — all fields are C ABI integers/pointers.
#[repr(C)]
pub struct AecStream {
pub next_in: *const u8,
pub avail_in: usize,
pub total_in: usize,
pub next_out: *mut u8,
pub avail_out: usize,
pub total_out: usize,
pub bits_per_sample: u32,
pub block_size: u32,
pub rsi: u32,
pub flags: u32,
/// Opaque internal state; initialised to null, set by libaec on first call.
pub state: *mut c_void,
}
impl AecStream {
/// Return a zero-initialised stream safe to pass to libaec.
pub fn zeroed() -> Self {
Self {
next_in: std::ptr::null(),
avail_in: 0,
total_in: 0,
next_out: std::ptr::null_mut(),
avail_out: 0,
total_out: 0,
bits_per_sample: 0,
block_size: 0,
rsi: 0,
flags: 0,
state: std::ptr::null_mut(),
}
}
}
unsafe extern "C" { unsafe extern "C" {
/// One-shot decompression. Returns 0 on success. /// One-shot decompression. Returns `AEC_OK` (0) on success.
/// ///
/// # Safety /// # Safety
/// `src` must be valid for `src_len` bytes; `dst` must be valid for `*dst_len` bytes. /// `strm.next_in` must be valid for `strm.avail_in` bytes;
pub fn aec_buffer_decode( /// `strm.next_out` must be valid for `strm.avail_out` bytes.
src: *const u8, pub fn aec_buffer_decode(strm: *mut AecStream) -> i32;
src_len: usize,
dst: *mut u8,
dst_len: *mut usize,
bits_per_sample: u32,
block_size: u32,
flags: u32,
) -> i32;
} }
#[cfg(test)] #[cfg(test)]
@@ -33,4 +67,12 @@ mod tests {
assert_eq!(AEC_DATA_PREPROCESS, 1); assert_eq!(AEC_DATA_PREPROCESS, 1);
assert_eq!(AEC_DATA_MSB, 2); assert_eq!(AEC_DATA_MSB, 2);
} }
#[test]
fn aec_stream_zeroed_has_null_ptrs() {
let s = AecStream::zeroed();
assert!(s.next_in.is_null());
assert!(s.next_out.is_null());
assert!(s.state.is_null());
}
} }