use std::io::{BufReader, Read, Write};
use crate::{
constants::ZSTD_DICTIONARY_MAGIC, error::CarbonadoError,
filepack_manifest::MAX_SEGMENT_MAIN_LEN,
};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ZstdEncode {
pub level: Option<i32>,
pub dict: Option<Vec<u8>>,
}
impl ZstdEncode {
pub fn level(level: i32) -> Self {
Self {
level: Some(level),
dict: None,
}
}
pub fn with_dict(level: i32, dict: Vec<u8>) -> Self {
Self {
level: Some(level),
dict: Some(dict),
}
}
}
pub fn require_zstd_level(zstd: &ZstdEncode) -> Result<i32, CarbonadoError> {
zstd.level.ok_or(CarbonadoError::MissingZstdLevel)
}
pub fn zstd_dictionary_id(dict: &[u8]) -> Option<u32> {
if dict.len() < 8 || dict[0..4] != ZSTD_DICTIONARY_MAGIC {
return None;
}
Some(u32::from_le_bytes(dict[4..8].try_into().ok()?))
}
pub fn zstd_frame_dictionary_id(frame: &[u8]) -> Result<Option<u32>, CarbonadoError> {
if frame.len() < 5 {
return Err(CarbonadoError::ZstdError(
"truncated zstd frame header".into(),
));
}
if frame[0..4] != crate::constants::ZSTD_MAGIC {
return Ok(None);
}
let descriptor = frame[4];
if (descriptor & 0x08) != 0 {
return Err(CarbonadoError::ZstdError("zstd reserved bit set".into()));
}
let dictionary_id_flag = descriptor & 0x03;
let single_segment = (descriptor & 0x20) != 0;
let need_win = if single_segment { 0 } else { 1 };
let did_sz = match dictionary_id_flag {
0 => 0,
1 => 1,
2 => 2,
3 => 4,
_ => 0,
};
let header_len = 5 + need_win + did_sz;
if frame.len() < header_len {
return Err(CarbonadoError::ZstdError(
"truncated zstd frame header".into(),
));
}
let did_off = 5 + need_win;
let id = match did_sz {
0 => None,
1 => Some(u32::from(frame[did_off])),
2 => Some(u32::from(u16::from_le_bytes([
frame[did_off],
frame[did_off + 1],
]))),
4 => Some(u32::from_le_bytes(
frame[did_off..did_off + 4]
.try_into()
.map_err(|_| CarbonadoError::ZstdError("truncated dictionary id".into()))?,
)),
_ => None,
};
Ok(id)
}
struct CountWriter<W> {
inner: W,
count: u64,
max: Option<u64>,
}
impl<W: Write> Write for CountWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let next = self.count.saturating_add(buf.len() as u64);
if let Some(max) = self.max
&& next > max
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"decompressed output exceeds maximum allowed size",
));
}
let n = self.inner.write(buf)?;
self.count += n as u64;
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
pub fn stream_compress<R: Read, W: Write>(
input: R,
output: W,
level: i32,
) -> Result<u64, CarbonadoError> {
stream_compress_with_dict(input, output, level, None)
}
pub fn stream_compress_with_dict<R: Read, W: Write>(
mut input: R,
output: W,
level: i32,
dict: Option<&[u8]>,
) -> Result<u64, CarbonadoError> {
let mut counter = CountWriter {
inner: output,
count: 0,
max: None,
};
if let Some(dict) = dict.filter(|d| !d.is_empty()) {
let mut encoder = zstd::stream::Encoder::with_dictionary(&mut counter, level, dict)
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
encoder
.include_checksum(false)
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
std::io::copy(&mut input, &mut encoder).map_err(CarbonadoError::StdIoError)?;
encoder
.finish()
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
} else {
zstd::stream::copy_encode(&mut input, &mut counter, level)
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
}
Ok(counter.count)
}
pub fn stream_decompress<R: Read, W: Write>(input: R, output: W) -> Result<u64, CarbonadoError> {
stream_decompress_with_dict(input, output, None)
}
pub fn stream_decompress_with_dict<R: Read, W: Write>(
input: R,
output: W,
dict: Option<&[u8]>,
) -> Result<u64, CarbonadoError> {
let mut counter = CountWriter {
inner: output,
count: 0,
max: Some(MAX_SEGMENT_MAIN_LEN),
};
let mut prefixed = PrefixRead {
prefix: Vec::new(),
inner: input,
pos: 0,
};
let mut hdr = [0u8; 16];
let n = prefixed
.fill_prefix(&mut hdr)
.map_err(CarbonadoError::StdIoError)?;
let frame_id = zstd_frame_dictionary_id(&hdr[..n])?;
if let Some(id) = frame_id {
let dict_bytes = dict
.filter(|d| !d.is_empty())
.ok_or(CarbonadoError::MissingZstdDictionary { dictionary_id: id })?;
let mut decoder =
zstd::stream::Decoder::with_dictionary(BufReader::new(&mut prefixed), dict_bytes)
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
std::io::copy(&mut decoder, &mut counter).map_err(CarbonadoError::StdIoError)?;
} else {
zstd::stream::copy_decode(&mut prefixed, &mut counter)
.map_err(|e| CarbonadoError::ZstdError(e.to_string()))?;
}
Ok(counter.count)
}
pub fn compress_buffer(input: &[u8], level: i32) -> Result<Vec<u8>, CarbonadoError> {
let mut out = Vec::new();
stream_compress(input, &mut out, level)?;
Ok(out)
}
pub fn compress_buffer_with_dict(
input: &[u8],
level: i32,
dict: &[u8],
) -> Result<Vec<u8>, CarbonadoError> {
let mut out = Vec::new();
stream_compress_with_dict(input, &mut out, level, Some(dict))?;
Ok(out)
}
pub fn decompress_buffer(input: &[u8]) -> Result<Vec<u8>, CarbonadoError> {
decompress_buffer_with_dict(input, None)
}
pub fn decompress_buffer_with_dict(
input: &[u8],
dict: Option<&[u8]>,
) -> Result<Vec<u8>, CarbonadoError> {
let mut out = Vec::new();
stream_decompress_with_dict(input, &mut out, dict)?;
Ok(out)
}
struct PrefixRead<R> {
prefix: Vec<u8>,
inner: R,
pos: usize,
}
impl<R: Read> PrefixRead<R> {
fn fill_prefix(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
self.prefix.extend_from_slice(&buf[..n]);
Ok(n)
}
}
impl<R: Read> Read for PrefixRead<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pos < self.prefix.len() {
let n = (self.prefix.len() - self.pos).min(buf.len());
buf[..n].copy_from_slice(&self.prefix[self.pos..self.pos + n]);
self.pos += n;
return Ok(n);
}
self.inner.read(buf)
}
}