1use core::ffi::c_void;
11
12use crate::{
13 ZSTD_compress, ZSTD_compressBound, ZSTD_createDCtx, ZSTD_decompress, ZSTD_decompressDCtx,
14 ZSTD_freeDCtx, ZSTD_getErrorName, ZSTD_getFrameContentSize, ZSTD_isError, ZSTD_DCtx,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Error(pub usize);
20
21impl core::fmt::Display for Error {
22 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23 let name = unsafe { core::ffi::CStr::from_ptr(ZSTD_getErrorName(self.0)) };
25 write!(f, "zstd error: {}", name.to_string_lossy())
26 }
27}
28
29impl std::error::Error for Error {}
30
31pub type Result<T> = core::result::Result<T, Error>;
32
33#[inline]
34fn check(code: usize) -> Result<usize> {
35 if unsafe { ZSTD_isError(code) } != 0 {
37 Err(Error(code))
38 } else {
39 Ok(code)
40 }
41}
42
43#[inline]
45#[must_use]
46pub fn compress_bound(src_len: usize) -> usize {
47 unsafe { ZSTD_compressBound(src_len) }
49}
50
51#[inline]
54#[must_use]
55pub fn frame_content_size(src: &[u8]) -> Option<usize> {
56 let s = unsafe { ZSTD_getFrameContentSize(src.as_ptr() as *const c_void, src.len()) };
58 if s == u64::MAX || s == u64::MAX - 1 {
60 None
61 } else {
62 Some(s as usize)
63 }
64}
65
66pub fn compress(src: &[u8], level: i32) -> Result<Vec<u8>> {
68 let cap = compress_bound(src.len());
69 let mut dst = vec![0u8; cap];
70 let n = check(unsafe {
72 ZSTD_compress(
73 dst.as_mut_ptr() as *mut c_void,
74 cap,
75 src.as_ptr() as *const c_void,
76 src.len(),
77 level,
78 )
79 })?;
80 dst.truncate(n);
81 Ok(dst)
82}
83
84pub fn decompress(src: &[u8]) -> Result<Vec<u8>> {
88 let cap = frame_content_size(src).ok_or(Error(0))?;
89 let mut dst = vec![0u8; cap];
90 let n = decompress_into(src, &mut dst)?;
91 debug_assert_eq!(n, cap);
92 dst.truncate(n);
93 Ok(dst)
94}
95
96pub fn decompress_into(src: &[u8], dst: &mut [u8]) -> Result<usize> {
100 check(unsafe {
102 ZSTD_decompress(
103 dst.as_mut_ptr() as *mut c_void,
104 dst.len(),
105 src.as_ptr() as *const c_void,
106 src.len(),
107 )
108 })
109}
110
111pub struct Decompressor {
115 dctx: *mut ZSTD_DCtx,
116}
117
118impl Decompressor {
119 #[must_use]
121 pub fn new() -> Self {
122 let dctx = unsafe { ZSTD_createDCtx() };
124 assert!(!dctx.is_null(), "ZSTD_createDCtx returned null (OOM)");
125 Self { dctx }
126 }
127
128 pub fn decompress_into(&mut self, src: &[u8], dst: &mut [u8]) -> Result<usize> {
130 check(unsafe {
132 ZSTD_decompressDCtx(
133 self.dctx,
134 dst.as_mut_ptr() as *mut c_void,
135 dst.len(),
136 src.as_ptr() as *const c_void,
137 src.len(),
138 )
139 })
140 }
141}
142
143impl Default for Decompressor {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl Drop for Decompressor {
150 fn drop(&mut self) {
151 unsafe { ZSTD_freeDCtx(self.dctx) };
153 }
154}
155
156unsafe impl Send for Decompressor {}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn roundtrip_oneshot() {
166 let data = b"the quick brown fox jumps over the lazy dog".repeat(1000);
167 let comp = compress(&data, 3).unwrap();
168 assert!(comp.len() < data.len());
169 assert_eq!(frame_content_size(&comp), Some(data.len()));
170 assert_eq!(decompress(&comp).unwrap(), data);
171 }
172
173 #[test]
174 fn roundtrip_zero_copy_reused_ctx() {
175 let frames: Vec<Vec<u8>> = (0..16)
176 .map(|i| compress(format!("frame {i} ").repeat(500).as_bytes(), 3).unwrap())
177 .collect();
178 let mut dctx = Decompressor::new();
179 let mut buf = vec![0u8; 64 * 1024];
180 for (i, f) in frames.iter().enumerate() {
181 let n = dctx.decompress_into(f, &mut buf).unwrap();
182 assert_eq!(&buf[..n], format!("frame {i} ").repeat(500).as_bytes());
183 }
184 }
185
186 #[test]
187 fn error_displays() {
188 assert!(decompress_into(b"not a zstd frame", &mut [0u8; 16]).is_err());
190 }
191}