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:
co-authored by
Claude Sonnet 4.6
parent
d6c4d4f111
commit
e91f7fc539
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user