use std::convert::TryInto;
use std::mem;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use libc::{c_int, c_long, c_short, c_uint, c_void};
use thiserror::Error;
const UCL_VERSION: u32 = 0x01_0300;
#[link(name = "ucl")]
extern "C" {
#[must_use]
fn __ucl_init2(
version: u32,
short: i32,
int: i32,
long: i32,
ucl_uint32: i32,
ucl_uint: i32,
minus_one: i32,
pchar: i32,
ucl_voidp: i32,
ucl_compress_t: i32,
) -> c_int;
#[must_use]
fn ucl_nrv2b_decompress_safe_8(
src: *const u8,
src_len: c_uint,
dst: *mut u8,
dst_len: *mut c_uint,
wrkmem: *const c_void,
) -> c_int;
#[must_use]
fn ucl_nrv2b_99_compress(
src: *const u8,
src_len: c_uint,
dst: *mut u8,
dst_len: *mut c_uint,
cb: *const c_void,
level: c_int,
conf: *const c_void,
result: *const c_void,
) -> c_int;
}
static INITIALIZED: AtomicBool = AtomicBool::new(false);
pub fn ucl_init() {
unsafe {
let res = __ucl_init2(
UCL_VERSION,
mem::size_of::<c_short>() as i32,
mem::size_of::<c_int>() as i32,
mem::size_of::<c_long>() as i32,
mem::size_of::<u32>() as i32,
mem::size_of::<c_uint>() as i32,
-1i32,
mem::size_of::<*mut u8>() as i32,
mem::size_of::<*mut c_void>() as i32,
mem::size_of::<*mut c_void>() as i32, );
assert!(
res == 0,
"ucl init failed. incompatible library version or architecture?"
);
}
INITIALIZED.store(true, Ordering::Release);
}
#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)]
pub enum UclErrorKind {
#[error("generic UCL error")]
GenericError,
#[error("invalid argument")]
InvalidArgument,
#[error("out of memory")]
OutOfMemory,
#[error("not compressible")]
NotCompressible,
#[error("input overrun")]
InputOverrun,
#[error("output overrun")]
OutputOverrun,
#[error("look-behind overrun")]
LookbehindOverrun,
#[error("EOF not found")]
EofNotFound,
#[error("input not consumed")]
InputNotConsumed,
#[error("overlap overrun")]
OverlapOverrun,
#[error("src buffer too large")]
SrcTooLarge,
#[error("dst buffer too large")]
DstTooLarge,
#[error("dst buffer too small")]
DstTooSmall,
}
impl UclErrorKind {
fn from(code: i32) -> Self {
match code {
-2 => UclErrorKind::InvalidArgument,
-3 => UclErrorKind::OutOfMemory,
-101 => UclErrorKind::NotCompressible,
-201 => UclErrorKind::InputOverrun,
-202 => UclErrorKind::OutputOverrun,
-203 => UclErrorKind::LookbehindOverrun,
-204 => UclErrorKind::EofNotFound,
-205 => UclErrorKind::InputNotConsumed,
-206 => UclErrorKind::OverlapOverrun,
_ => UclErrorKind::GenericError,
}
}
}
unsafe fn decompress_ptr(
src: &[u8],
dst: *mut u8,
dst_capacity: u32,
) -> std::result::Result<u32, UclErrorKind> {
assert!(
INITIALIZED.load(Ordering::Acquire),
"ucl_init was not called before attempting decompression"
);
let src_len = match src.len().try_into() {
Ok(v) => v,
Err(_) => return Err(UclErrorKind::SrcTooLarge),
};
let mut dst_len = dst_capacity;
let res = ucl_nrv2b_decompress_safe_8(src.as_ptr(), src_len, dst, &mut dst_len, ptr::null());
match res {
0 => {
assert!(
dst_len <= (dst_capacity as u32),
"decompressen yielded more data than available in dst buffer"
);
Ok(dst_len)
}
_ => Err(UclErrorKind::from(res)),
}
}
pub fn decompress_into_buffer(
src: &[u8],
dst: &mut [u8],
) -> std::result::Result<u32, UclErrorKind> {
let dst_len = match dst.len().try_into() {
Ok(v) => v,
Err(_) => return Err(UclErrorKind::DstTooLarge),
};
unsafe { decompress_ptr(src, dst.as_mut_ptr(), dst_len) }
}
pub fn decompress(src: &[u8], dst_capacity: u32) -> std::result::Result<Vec<u8>, UclErrorKind> {
let mut dst = Vec::with_capacity(dst_capacity as usize);
unsafe {
let new_length = decompress_ptr(src, dst.as_mut_ptr(), dst_capacity)?;
dst.set_len(new_length as usize);
}
Ok(dst)
}
pub const fn minimum_compression_buffer_size(src_len: usize) -> usize {
src_len + (src_len / 8) + 256
}
unsafe fn compress_ptr(
src: &[u8],
dst: *mut u8,
dst_capacity: u32,
) -> std::result::Result<u32, UclErrorKind> {
assert!(
INITIALIZED.load(Ordering::Acquire),
"ucl_init was not called before attempting decompression"
);
let src_len = match src.len().try_into() {
Ok(v) => v,
Err(_) => return Err(UclErrorKind::SrcTooLarge),
};
let mut dst_len = dst_capacity;
let res = ucl_nrv2b_99_compress(
src.as_ptr(),
src_len,
dst,
&mut dst_len,
ptr::null(),
6,
ptr::null(),
ptr::null(),
);
match res {
0 => {
assert!(
dst_len <= (dst_capacity as u32),
"decompressen yielded more data than available in dst buffer"
);
Ok(dst_len)
}
_ => Err(UclErrorKind::from(res)),
}
}
pub fn compress_into_buffer(src: &[u8], dst: &mut [u8]) -> std::result::Result<u32, UclErrorKind> {
if dst.len() < minimum_compression_buffer_size(src.len()) {
return Err(UclErrorKind::DstTooSmall);
}
let dst_len = match dst.len().try_into() {
Ok(v) => v,
Err(_) => return Err(UclErrorKind::DstTooLarge),
};
unsafe { compress_ptr(src, dst.as_mut_ptr(), dst_len) }
}
pub fn compress(src: &[u8]) -> std::result::Result<Vec<u8>, UclErrorKind> {
let capacity = minimum_compression_buffer_size(src.len());
let mut dst = Vec::with_capacity(capacity);
let dst_len = match capacity.try_into() {
Ok(v) => v,
Err(_) => return Err(UclErrorKind::DstTooLarge),
};
unsafe {
let new_length = compress_ptr(src, dst.as_mut_ptr(), dst_len)?;
dst.set_len(new_length as usize);
}
Ok(dst)
}
#[cfg(test)]
mod tests {
use super::{compress_into_buffer, decompress, decompress_into_buffer, ucl_init, UclErrorKind};
#[test]
fn compress_buffer_nothing() {
ucl_init();
let mut buf = [0xa5u8; 256];
assert_eq!(compress_into_buffer(&[], &mut buf).unwrap(), 8);
assert_eq!(&buf[..8], b"\x00\x00\x00\x00\x00\x04\x80\xff");
assert!(&buf[8..].iter().all(|b| *b == 0xa5));
}
#[test]
fn compress_buffer_too_small() {
ucl_init();
let mut buf = [0xa5u8; 4];
assert_eq!(
compress_into_buffer(b"\xde\xad\xbe\xef", &mut buf).unwrap_err(),
UclErrorKind::DstTooSmall
);
assert!(buf.iter().all(|b| *b == 0xa5));
}
#[test]
fn compress_buffer_too_big() {
ucl_init();
let mut buf = vec![0u8; 4 * 1024 * 1024 * 1024];
assert_eq!(
compress_into_buffer(b"\xde\xad\xbe\xef", &mut buf).unwrap_err(),
UclErrorKind::DstTooLarge
);
}
#[test]
fn decompress_buffer_dst_too_big() {
ucl_init();
let mut buf = vec![0u8; 4 * 1024 * 1024 * 1024];
assert_eq!(
decompress_into_buffer(b"\xde\xad\xbe\xef", &mut buf).unwrap_err(),
UclErrorKind::DstTooLarge
);
}
#[test]
fn decompress_buffer_src_too_big() {
ucl_init();
let mut buf = vec![0u8; 4];
let input = vec![0u8; 4 * 1024 * 1024 * 1024];
assert_eq!(
decompress_into_buffer(&input, &mut buf).unwrap_err(),
UclErrorKind::SrcTooLarge
);
}
#[test]
fn decompress_buffer_nothing() {
ucl_init();
let compressed = b"\x00\x00\x00\x00\x00\x04\x80\xff";
let mut buf = [0xa5u8; 8];
assert_eq!(
decompress_into_buffer(compressed.as_ref(), &mut buf).unwrap(),
0
);
assert!(buf.iter().all(|b| *b == 0xa5));
}
#[test]
fn decompress_buffer_8k_too_small() {
ucl_init();
let compressed =
b"\x92\x00\xaa\xa8\xc9\x55\x54\x64\xaa\xaa\x32\x55\x55\x08\x00\x00\x00\x00\x00\x24\xff";
let mut buf = [0xa5u8; 8191];
assert_eq!(
decompress_into_buffer(compressed.as_ref(), &mut buf).unwrap_err(),
UclErrorKind::OutputOverrun
);
}
#[test]
fn decompress_src_too_big() {
ucl_init();
let input = vec![0u8; 4 * 1024 * 1024 * 1024];
assert_eq!(
decompress(&input, 4).unwrap_err(),
UclErrorKind::SrcTooLarge
);
}
#[test]
fn decompress_nothing() {
ucl_init();
let compressed = b"\x00\x00\x00\x00\x00\x04\x80\xff";
assert_eq!(decompress(compressed.as_ref(), 8).unwrap(), b"");
}
#[test]
fn decompress_8k_too_small() {
ucl_init();
let compressed =
b"\x92\x00\xaa\xa8\xc9\x55\x54\x64\xaa\xaa\x32\x55\x55\x08\x00\x00\x00\x00\x00\x24\xff";
assert_eq!(
decompress(compressed.as_ref(), 8191).unwrap_err(),
UclErrorKind::OutputOverrun
);
}
}