diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index d928b1d3e..9eabaa7a3 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -59,19 +59,9 @@ /*-************************************* * Helper functions ***************************************/ -/* ZSTD_compressBound() - * Note that the result from this function is only valid for - * the one-pass compression functions. - * When employing the streaming mode, - * if flushes are frequently altering the size of blocks, - * the overhead from block headers can make the compressed data larger - * than the return value of ZSTD_compressBound(). - */ -size_t ZSTD_compressBound(size_t srcSize) { - size_t const r = ZSTD_COMPRESSBOUND(srcSize); - if (r==0) return ERROR(srcSize_wrong); - return r; -} +/* ZSTD_compressBound() lives in rust/src/zstd_compress_api.rs. Keep this + * translation unit for the context implementation, which still calls the + * exported ABI symbol. */ /*-************************************* diff --git a/rust/src/lib.rs b/rust/src/lib.rs index bc7d8909d..54d299449 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -22,6 +22,8 @@ pub mod threading; pub mod xxhash; pub mod zstd_common; #[cfg(feature = "compression")] +pub mod zstd_compress_api; +#[cfg(feature = "compression")] pub mod zstd_compress_literals; #[cfg(feature = "compression")] pub mod zstd_compress_sequences; diff --git a/rust/src/zstd_compress_api.rs b/rust/src/zstd_compress_api.rs new file mode 100644 index 000000000..8f57cae8e --- /dev/null +++ b/rust/src/zstd_compress_api.rs @@ -0,0 +1,45 @@ +//! Public, context-free compression API helpers. +//! +//! The stateful compression context remains in the C translation unit for +//! now. These helpers have no context layout dependency, so they can expose +//! the public ABI directly from Rust. + +use crate::errors::{ZstdErrorCode, ERROR}; + +#[cfg(target_pointer_width = "64")] +const ZSTD_MAX_INPUT_SIZE: usize = 0xFF00_FF00_FF00_FF00; +#[cfg(target_pointer_width = "32")] +const ZSTD_MAX_INPUT_SIZE: usize = 0xFF00_FF00; + +const SMALL_INPUT_THRESHOLD: usize = 128 << 10; + +/// Returns the maximum one-pass compressed size, matching +/// `ZSTD_COMPRESSBOUND()` and its public error contract. +#[no_mangle] +pub extern "C" fn ZSTD_compressBound(src_size: usize) -> usize { + if src_size >= ZSTD_MAX_INPUT_SIZE { + return ERROR(ZstdErrorCode::SrcSizeWrong); + } + + let margin = if src_size < SMALL_INPUT_THRESHOLD { + (SMALL_INPUT_THRESHOLD - src_size) >> 11 + } else { + 0 + }; + src_size.wrapping_add(src_size >> 8).wrapping_add(margin) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::errors::ERR_isError; + + #[test] + fn compress_bound_matches_the_public_macro_edges() { + assert_eq!(ZSTD_compressBound(0), 64); + assert_eq!(ZSTD_compressBound(1), 64); + assert_eq!(ZSTD_compressBound(SMALL_INPUT_THRESHOLD - 1), 131_582); + assert_eq!(ZSTD_compressBound(SMALL_INPUT_THRESHOLD), 131_584); + assert!(ERR_isError(ZSTD_compressBound(ZSTD_MAX_INPUT_SIZE))); + } +}