use std::panic::catch_unwind;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wuff_woff2_compute_final_size(data: *const u8, length: usize) -> usize {
if data.is_null() || length < 20 {
return 0;
}
let bytes = unsafe { std::slice::from_raw_parts(data, length) };
u32::from_be_bytes(bytes[16..20].try_into().unwrap()) as usize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wuff_woff2_decode(
data: *const u8,
length: usize,
result_length: *mut usize,
) -> *mut u8 {
unsafe { *result_length = 0 };
if data.is_null() {
return std::ptr::null_mut();
}
let bytes = unsafe { std::slice::from_raw_parts(data, length) };
let result = catch_unwind(|| wuff::decompress_woff2(bytes));
match result {
Ok(Ok(decompressed)) => {
let boxed: Box<[u8]> = decompressed.into_boxed_slice();
let len = boxed.len();
let ptr = Box::into_raw(boxed) as *mut u8;
unsafe { *result_length = len };
ptr
}
Ok(Err(_)) | Err(_) => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wuff_woff2_free(ptr: *mut u8, length: usize) {
if ptr.is_null() {
return;
}
let slice = std::ptr::slice_from_raw_parts_mut(ptr, length);
drop(unsafe { Box::from_raw(slice) });
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_final_size_null_and_short() {
unsafe {
assert_eq!(wuff_woff2_compute_final_size(std::ptr::null(), 0), 0);
let short = [0u8; 19];
assert_eq!(
wuff_woff2_compute_final_size(short.as_ptr(), short.len()),
0
);
}
}
#[test]
fn compute_final_size_reads_total_sfnt_size() {
let mut header = [0u8; 48];
header[16..20].copy_from_slice(&0x0001_2345u32.to_be_bytes());
unsafe {
assert_eq!(
wuff_woff2_compute_final_size(header.as_ptr(), header.len()),
0x0001_2345
);
}
}
#[test]
fn decode_invalid_data_returns_null() {
let garbage = [0u8; 64];
let mut len = usize::MAX;
let ptr = unsafe { wuff_woff2_decode(garbage.as_ptr(), garbage.len(), &mut len) };
assert!(ptr.is_null());
assert_eq!(len, 0);
}
#[test]
fn free_null_is_noop() {
unsafe { wuff_woff2_free(std::ptr::null_mut(), 0) };
}
}