use core::{marker::PhantomData, ptr::NonNull};
use crate::{
parse_code, ptr_mut, ptr_void, CompressionLevel, ErrorCode, InBuffer,
OutBuffer, Poison, SafeResult, WriteBuf, SEEKABLE_FRAMEINDEX_TOOLARGE,
};
const INIT_MISSING_ERROR: ErrorCode =
(zstd_sys::ZSTD_ErrorCode::ZSTD_error_init_missing as usize)
.wrapping_neg();
const FRAMEINDEX_TOOLARGE_ERROR: ErrorCode =
(zstd_sys::ZSTD_ErrorCode::ZSTD_error_frameIndex_tooLarge as usize)
.wrapping_neg();
#[derive(Debug, PartialEq)]
pub struct FrameIndexTooLargeError;
impl core::fmt::Display for FrameIndexTooLargeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Frame index too large")
}
}
pub struct SeekableCStream(NonNull<zstd_sys::ZSTD_seekable_CStream>);
unsafe impl Send for SeekableCStream {}
unsafe impl Sync for SeekableCStream {}
impl Default for SeekableCStream {
fn default() -> Self {
SeekableCStream::create()
}
}
impl SeekableCStream {
pub fn try_create() -> Option<Self> {
Some(SeekableCStream(NonNull::new(unsafe {
zstd_sys::ZSTD_seekable_createCStream()
})?))
}
pub fn create() -> Self {
Self::try_create()
.expect("zstd returned null pointer when creating new seekable compression stream")
}
pub fn init(
&mut self,
compression_level: CompressionLevel,
checksum_flag: bool,
max_frame_size: u32,
) -> SafeResult {
let code = unsafe {
zstd_sys::ZSTD_seekable_initCStream(
self.0.as_ptr(),
compression_level,
checksum_flag as i32,
max_frame_size,
)
};
parse_code(code)
}
pub fn compress_stream<C: WriteBuf + ?Sized>(
&mut self,
output: &mut OutBuffer<'_, C>,
input: &mut InBuffer<'_>,
) -> SafeResult {
let mut output = output.wrap();
let mut input = input.wrap();
let code = unsafe {
zstd_sys::ZSTD_seekable_compressStream(
self.0.as_ptr(),
ptr_mut(&mut output),
ptr_mut(&mut input),
)
};
let res = output.check(parse_code(code));
input.check(res)
}
pub fn end_frame<C: WriteBuf + ?Sized>(
&mut self,
output: &mut OutBuffer<'_, C>,
) -> SafeResult {
let mut output = output.wrap();
let code = unsafe {
zstd_sys::ZSTD_seekable_endFrame(
self.0.as_ptr(),
ptr_mut(&mut output),
)
};
output.check(parse_code(code))
}
pub fn end_stream<C: WriteBuf + ?Sized>(
&mut self,
output: &mut OutBuffer<'_, C>,
) -> SafeResult {
let mut output = output.wrap();
let code = unsafe {
zstd_sys::ZSTD_seekable_endStream(
self.0.as_ptr(),
ptr_mut(&mut output),
)
};
output.check(parse_code(code))
}
}
impl Drop for SeekableCStream {
fn drop(&mut self) {
unsafe {
zstd_sys::ZSTD_seekable_freeCStream(self.0.as_ptr());
}
}
}
pub struct FrameLog(NonNull<zstd_sys::ZSTD_frameLog>);
unsafe impl Send for FrameLog {}
unsafe impl Sync for FrameLog {}
impl FrameLog {
pub fn try_create(checksum_flag: bool) -> Option<Self> {
Some(FrameLog(
NonNull::new(unsafe {
zstd_sys::ZSTD_seekable_createFrameLog(checksum_flag as i32)
})?,
))
}
pub fn create(checksum_flag: bool) -> Self {
Self::try_create(checksum_flag)
.expect("Zstd returned null pointer when creating new frame log")
}
pub fn log_frame(
&mut self,
compressed_size: u32,
decompressed_size: u32,
checksum: Option<u32>,
) -> SafeResult {
let code = unsafe {
zstd_sys::ZSTD_seekable_logFrame(
self.0.as_ptr(),
compressed_size,
decompressed_size,
checksum.unwrap_or_default(),
)
};
parse_code(code)
}
pub fn write_seek_table<C: WriteBuf + ?Sized>(
&mut self,
output: &mut OutBuffer<'_, C>,
) -> SafeResult {
let mut output = output.wrap();
let code = unsafe {
zstd_sys::ZSTD_seekable_writeSeekTable(
self.0.as_ptr(),
ptr_mut(&mut output),
)
};
output.check(parse_code(code))
}
}
impl Drop for FrameLog {
fn drop(&mut self) {
unsafe {
zstd_sys::ZSTD_seekable_freeFrameLog(self.0.as_ptr());
}
}
}
pub struct Seekable<'a>(
NonNull<zstd_sys::ZSTD_seekable>,
PhantomData<&'a ()>,
Poison,
);
unsafe impl Send for Seekable<'_> {}
unsafe impl Sync for Seekable<'_> {}
impl Default for Seekable<'_> {
fn default() -> Self {
Seekable::create()
}
}
impl<'a> Seekable<'a> {
pub fn try_create() -> Option<Self> {
Some(Seekable(
NonNull::new(unsafe { zstd_sys::ZSTD_seekable_create() })?,
PhantomData,
Poison(Some(INIT_MISSING_ERROR)),
))
}
pub fn create() -> Self {
Self::try_create()
.expect("Zstd returned null pointer when creating new seekable")
}
pub fn init_buff(&mut self, src: &'a [u8]) -> SafeResult {
let code = unsafe {
zstd_sys::ZSTD_seekable_initBuff(
self.0.as_ptr(),
ptr_void(src),
src.len(),
)
};
self.2.clear();
self.2.record(parse_code(code))
}
pub fn decompress<C: WriteBuf + ?Sized>(
&mut self,
dst: &mut C,
offset: u64,
) -> SafeResult {
self.2.guard()?;
let remaining = self.decompressed_size().saturating_sub(offset);
unsafe {
dst.write_from(|buffer, capacity| {
if remaining == 0 {
return Ok(0);
}
parse_code(zstd_sys::ZSTD_seekable_decompress(
self.0.as_ptr(),
buffer,
remaining.min(capacity as u64) as usize,
offset,
))
})
}
}
fn decompressed_size(&self) -> u64 {
let last_frame = match self.num_frames().checked_sub(1) {
Some(last_frame) => last_frame,
None => return 0,
};
let offset = unsafe {
zstd_sys::ZSTD_seekable_getFrameDecompressedOffset(
self.0.as_ptr(),
last_frame,
)
};
let size = unsafe {
zstd_sys::ZSTD_seekable_getFrameDecompressedSize(
self.0.as_ptr(),
last_frame,
)
};
offset.wrapping_add(size as u64)
}
pub fn decompress_frame<C: WriteBuf + ?Sized>(
&mut self,
dst: &mut C,
frame_index: u32,
) -> SafeResult {
self.2.guard()?;
unsafe {
dst.write_from(|buffer, capacity| {
parse_code(zstd_sys::ZSTD_seekable_decompressFrame(
self.0.as_ptr(),
buffer,
capacity,
frame_index,
))
})
}
}
pub fn num_frames(&self) -> u32 {
unsafe { zstd_sys::ZSTD_seekable_getNumFrames(self.0.as_ptr()) }
}
pub fn frame_compressed_offset(
&self,
frame_index: u32,
) -> Result<u64, FrameIndexTooLargeError> {
let offset = unsafe {
zstd_sys::ZSTD_seekable_getFrameCompressedOffset(
self.0.as_ptr(),
frame_index,
)
};
if offset == SEEKABLE_FRAMEINDEX_TOOLARGE {
return Err(FrameIndexTooLargeError);
}
Ok(offset)
}
pub fn frame_decompressed_offset(
&self,
frame_index: u32,
) -> Result<u64, FrameIndexTooLargeError> {
let offset = unsafe {
zstd_sys::ZSTD_seekable_getFrameDecompressedOffset(
self.0.as_ptr(),
frame_index,
)
};
if offset == SEEKABLE_FRAMEINDEX_TOOLARGE {
return Err(FrameIndexTooLargeError);
}
Ok(offset)
}
pub fn frame_compressed_size(&self, frame_index: u32) -> SafeResult {
let code = unsafe {
zstd_sys::ZSTD_seekable_getFrameCompressedSize(
self.0.as_ptr(),
frame_index,
)
};
parse_code(code)
}
pub fn frame_decompressed_size(&self, frame_index: u32) -> SafeResult {
self.2.guard()?;
if frame_index >= self.num_frames() {
return Err(FRAMEINDEX_TOOLARGE_ERROR);
}
let code = unsafe {
zstd_sys::ZSTD_seekable_getFrameDecompressedSize(
self.0.as_ptr(),
frame_index,
)
};
parse_code(code)
}
pub fn offset_to_frame_index(&self, offset: u64) -> u32 {
if self.2.guard().is_err() {
return 0;
}
unsafe {
zstd_sys::ZSTD_seekable_offsetToFrameIndex(self.0.as_ptr(), offset)
}
}
}
impl<'a> Drop for Seekable<'a> {
fn drop(&mut self) {
unsafe {
zstd_sys::ZSTD_seekable_free(self.0.as_ptr());
}
}
}
#[cfg(feature = "std")]
pub struct AdvancedSeekable<'a, F> {
inner: Seekable<'a>,
src: *mut AdvancedSource<F>,
}
#[cfg(feature = "std")]
struct AdvancedSource<F> {
src: std::boxed::Box<F>,
panic: Option<std::boxed::Box<dyn core::any::Any + Send>>,
}
#[cfg(feature = "std")]
impl<F> std::panic::RefUnwindSafe for AdvancedSource<F> where
F: std::panic::RefUnwindSafe
{
}
#[cfg(feature = "std")]
unsafe impl<F> Send for AdvancedSeekable<'_, F> where F: Send {}
#[cfg(feature = "std")]
unsafe impl<F> Sync for AdvancedSeekable<'_, F> where F: Sync {}
#[cfg(feature = "std")]
impl<'a, F> core::ops::Deref for AdvancedSeekable<'a, F> {
type Target = Seekable<'a>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(feature = "std")]
impl<'a, F> AdvancedSeekable<'a, F> {
pub fn decompress<C: WriteBuf + ?Sized>(
&mut self,
dst: &mut C,
offset: u64,
) -> SafeResult {
let result = self.inner.decompress(dst, offset);
self.resume_panic();
result
}
pub fn decompress_frame<C: WriteBuf + ?Sized>(
&mut self,
dst: &mut C,
frame_index: u32,
) -> SafeResult {
let result = self.inner.decompress_frame(dst, frame_index);
self.resume_panic();
result
}
fn resume_panic(&mut self) {
if let Some(panic) = unsafe { (*self.src).panic.take() } {
std::panic::resume_unwind(panic);
}
}
}
#[cfg(feature = "std")]
impl<'a, F> Drop for AdvancedSeekable<'a, F> {
fn drop(&mut self) {
use std::boxed::Box;
let _: Box<AdvancedSource<F>> = unsafe { Box::from_raw(self.src) };
}
}
impl<'a> Seekable<'a> {
#[cfg(feature = "std")]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "std")))]
pub fn init_advanced<F>(
self,
src: std::boxed::Box<F>,
) -> Result<AdvancedSeekable<'a, F>, crate::ErrorCode>
where
F: std::io::Read + std::io::Seek,
{
let opaque =
std::boxed::Box::into_raw(std::boxed::Box::new(AdvancedSource {
src,
panic: None,
}));
let custom_file = zstd_sys::ZSTD_seekable_customFile {
opaque: opaque as *mut core::ffi::c_void,
read: Some(advanced_read::<F>),
seek: Some(advanced_seek::<F>),
};
let mut seekable = AdvancedSeekable {
inner: self,
src: opaque,
};
let code = unsafe {
zstd_sys::ZSTD_seekable_initAdvanced(
seekable.inner.0.as_ptr(),
custom_file,
)
};
seekable.resume_panic();
if crate::is_error(code) {
return Err(code);
}
seekable.inner.2.clear();
Ok(seekable)
}
}
#[cfg(feature = "std")]
unsafe fn advanced_call<F, T>(
opaque: *mut core::ffi::c_void,
f: impl FnOnce(&mut F) -> std::io::Result<T>,
) -> ::core::ffi::c_int {
use std::panic::{catch_unwind, AssertUnwindSafe};
let source = &mut *opaque.cast::<AdvancedSource<F>>();
let src = &mut *source.src;
match catch_unwind(AssertUnwindSafe(|| f(src).is_ok())) {
Ok(true) => 0,
Ok(false) => -1,
Err(panic) => {
source.panic = Some(panic);
-1
}
}
}
#[cfg(feature = "std")]
unsafe extern "C" fn advanced_seek<S: std::io::Seek>(
opaque: *mut core::ffi::c_void,
offset: ::core::ffi::c_longlong,
origin: ::core::ffi::c_int,
) -> ::core::ffi::c_int {
use core::convert::TryFrom;
use std::io::SeekFrom;
const SEEK_SET: i32 = 0;
const SEEK_CUR: i32 = 1;
const SEEK_END: i32 = 2;
let pos = match origin {
SEEK_SET => {
let offset = match u64::try_from(offset) {
Ok(offset) => offset,
Err(_) => return -1,
};
SeekFrom::Start(offset)
}
SEEK_CUR => SeekFrom::Current(offset),
SEEK_END => SeekFrom::End(offset),
_ => return -1,
};
advanced_call(opaque, |seeker: &mut S| seeker.seek(pos))
}
#[cfg(feature = "std")]
unsafe extern "C" fn advanced_read<R: std::io::Read>(
opaque: *mut core::ffi::c_void,
buffer: *mut core::ffi::c_void,
n: usize,
) -> ::core::ffi::c_int {
let buf = std::slice::from_raw_parts_mut(buffer as *mut u8, n);
advanced_call(opaque, |reader: &mut R| reader.read_exact(buf))
}
#[derive(Debug, PartialEq)]
pub struct SeekTableCreateError;
impl core::fmt::Display for SeekTableCreateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Zstd returned null pointer when creating new seektable from seekable")
}
}
pub struct SeekTable(NonNull<zstd_sys::ZSTD_seekTable>);
unsafe impl Send for SeekTable {}
unsafe impl Sync for SeekTable {}
impl SeekTable {
pub fn try_from_seekable<'a>(
value: &Seekable<'a>,
) -> Result<Self, SeekTableCreateError> {
let ptr = unsafe {
zstd_sys::ZSTD_seekTable_create_fromSeekable(value.0.as_ptr())
};
let ptr = NonNull::new(ptr).ok_or(SeekTableCreateError)?;
Ok(Self(ptr))
}
pub fn num_frames(&self) -> u32 {
unsafe { zstd_sys::ZSTD_seekTable_getNumFrames(self.0.as_ptr()) }
}
pub fn frame_compressed_offset(
&self,
frame_index: u32,
) -> Result<u64, FrameIndexTooLargeError> {
let offset = unsafe {
zstd_sys::ZSTD_seekTable_getFrameCompressedOffset(
self.0.as_ptr(),
frame_index,
)
};
if offset == SEEKABLE_FRAMEINDEX_TOOLARGE {
return Err(FrameIndexTooLargeError);
}
Ok(offset)
}
pub fn frame_decompressed_offset(
&self,
frame_index: u32,
) -> Result<u64, FrameIndexTooLargeError> {
let offset = unsafe {
zstd_sys::ZSTD_seekTable_getFrameDecompressedOffset(
self.0.as_ptr(),
frame_index,
)
};
if offset == SEEKABLE_FRAMEINDEX_TOOLARGE {
return Err(FrameIndexTooLargeError);
}
Ok(offset)
}
pub fn frame_compressed_size(&self, frame_index: u32) -> SafeResult {
let code = unsafe {
zstd_sys::ZSTD_seekTable_getFrameCompressedSize(
self.0.as_ptr(),
frame_index,
)
};
parse_code(code)
}
pub fn frame_decompressed_size(&self, frame_index: u32) -> SafeResult {
if frame_index >= self.num_frames() {
return Err(FRAMEINDEX_TOOLARGE_ERROR);
}
let code = unsafe {
zstd_sys::ZSTD_seekTable_getFrameDecompressedSize(
self.0.as_ptr(),
frame_index,
)
};
parse_code(code)
}
pub fn offset_to_frame_index(&self, offset: u64) -> u32 {
unsafe {
zstd_sys::ZSTD_seekTable_offsetToFrameIndex(
self.0.as_ptr(),
offset,
)
}
}
}
impl Drop for SeekTable {
fn drop(&mut self) {
unsafe {
zstd_sys::ZSTD_seekTable_free(self.0.as_ptr());
}
}
}