use std::{
io::{ErrorKind, Read, Write},
str::FromStr,
};
use brotli::enc::writer::CompressorWriter as BrotliEncoder;
use brotli::Decompressor as BrotliDecoder;
use flate2::{bufread::MultiGzDecoder, write::GzEncoder};
use flate2::{
bufread::{DeflateDecoder, ZlibDecoder},
write::{DeflateEncoder, ZlibEncoder},
Compression as GzCompression,
};
use zstd::stream::read::Decoder as ZstdDecoder;
use zstd::stream::write::Encoder as ZstdEncoder;
use crate::io::{ComboReader, CountRead, PeekRead, SourceCountRead};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionFormat {
Raw,
DeflateRaw,
DeflateZlib,
Gzip,
Brotli,
Zstd,
}
impl CompressionFormat {
pub fn as_coding_name_str(&self) -> &'static str {
match self {
CompressionFormat::Raw => "identity",
CompressionFormat::DeflateRaw => "deflate",
CompressionFormat::DeflateZlib => "deflate",
CompressionFormat::Gzip => "gzip",
CompressionFormat::Brotli => "br",
CompressionFormat::Zstd => "zstd",
}
}
}
impl FromStr for CompressionFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"identity" => Ok(Self::Raw),
"deflate" => Ok(Self::DeflateZlib),
"gzip" | "x-gzip" | "gz" => Ok(Self::Gzip),
"brotli" | "br" => Ok(Self::Brotli),
"zstd" => Ok(Self::Zstd),
_ => Err(()),
}
}
}
#[allow(clippy::large_enum_variant)]
enum Decoder<'a, S: Read> {
Raw(ComboReader<S>),
DeflateRaw(DeflateDecoder<ComboReader<S>>),
DeflateZlib(ZlibDecoder<ComboReader<S>>),
Gzip(MultiGzDecoder<ComboReader<S>>),
Brotli(BrotliDecoder<ComboReader<S>>),
Zstd(ZstdDecoder<'a, ComboReader<S>>),
}
impl<'a, S: Read> Decoder<'a, S> {
fn name(&self) -> &'static str {
match self {
Decoder::Raw(_) => "raw",
Decoder::DeflateRaw(_) => "deflate-raw",
Decoder::DeflateZlib(_) => "deflate-zlib",
Decoder::Gzip(_) => "gzip",
Decoder::Brotli(_) => "brotli",
Decoder::Zstd(_) => "zstd",
}
}
}
pub struct Decompressor<'a, S: Read> {
decoder: Decoder<'a, S>,
}
impl<'a, S: Read> Decompressor<'a, S> {
fn new_impl(stream: S, allow_unknown: bool) -> std::io::Result<Self> {
let mut stream = ComboReader::new(stream);
let magic_bytes = stream.peek_exact(4)?.to_vec();
tracing::debug!(?magic_bytes, "decompressor analysis");
let decoder = match &magic_bytes[0..4] {
[0x78, 0x01, _, _] | [0x78, 0x5e, _, _] | [0x78, 0x9c, _, _] | [0x78, 0xda, _, _] => {
Decoder::DeflateZlib(ZlibDecoder::new(stream))
}
[0x1f, 0x8b, _, _] => Decoder::Gzip(MultiGzDecoder::new(stream)),
[0x28, 0xb5, 0x2f, 0xfd] | [0x37, 0xa4, 0x30, 0xec] => {
Decoder::Zstd(ZstdDecoder::with_buffer(stream)?)
}
_ => {
if allow_unknown {
Decoder::Raw(stream)
} else {
return Err(ErrorKind::InvalidData.into());
}
}
};
tracing::debug!(decoder = decoder.name(), "decoder select");
Ok(Self { decoder })
}
pub fn new(stream: S) -> std::io::Result<Self> {
Self::new_impl(stream, false)
}
pub fn new_allow_unknown(stream: S) -> std::io::Result<Self> {
Self::new_impl(stream, true)
}
pub fn new_format(stream: S, format: CompressionFormat) -> std::io::Result<Self> {
let stream = ComboReader::new(stream);
let decoder = match format {
CompressionFormat::Raw => Decoder::Raw(stream),
CompressionFormat::DeflateRaw => Decoder::DeflateRaw(DeflateDecoder::new(stream)),
CompressionFormat::DeflateZlib => Decoder::DeflateZlib(ZlibDecoder::new(stream)),
CompressionFormat::Gzip => Decoder::Gzip(MultiGzDecoder::new(stream)),
CompressionFormat::Brotli => Decoder::Brotli(BrotliDecoder::new(stream, 4096)),
CompressionFormat::Zstd => Decoder::Zstd(ZstdDecoder::with_buffer(stream)?),
};
Ok(Self { decoder })
}
pub fn get_ref(&self) -> &S {
match &self.decoder {
Decoder::Raw(stream) => stream.get_ref(),
Decoder::DeflateRaw(stream) => stream.get_ref().get_ref(),
Decoder::DeflateZlib(stream) => stream.get_ref().get_ref(),
Decoder::Gzip(stream) => stream.get_ref().get_ref(),
Decoder::Brotli(stream) => stream.get_ref().get_ref(),
Decoder::Zstd(stream) => stream.get_ref().get_ref(),
}
}
pub fn get_mut(&mut self) -> &mut S {
match &mut self.decoder {
Decoder::Raw(stream) => stream.get_mut(),
Decoder::DeflateRaw(stream) => stream.get_mut().get_mut(),
Decoder::DeflateZlib(stream) => stream.get_mut().get_mut(),
Decoder::Gzip(stream) => stream.get_mut().get_mut(),
Decoder::Brotli(_stream) => unimplemented!(),
Decoder::Zstd(stream) => stream.get_mut().get_mut(),
}
}
pub fn into_inner(self) -> S {
match self.decoder {
Decoder::Raw(stream) => stream.into_inner(),
Decoder::DeflateRaw(stream) => stream.into_inner().into_inner(),
Decoder::DeflateZlib(stream) => stream.into_inner().into_inner(),
Decoder::Gzip(stream) => stream.into_inner().into_inner(),
Decoder::Brotli(stream) => stream.into_inner().into_inner(),
Decoder::Zstd(stream) => stream.finish().into_inner(),
}
}
}
impl<'a, S: Read> Read for Decompressor<'a, S> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.decoder {
Decoder::Raw(stream) => stream.read(buf),
Decoder::DeflateRaw(stream) => stream.read(buf),
Decoder::DeflateZlib(stream) => stream.read(buf),
Decoder::Gzip(stream) => stream.read(buf),
Decoder::Brotli(stream) => stream.read(buf),
Decoder::Zstd(stream) => stream.read(buf),
}
}
}
impl<'a, S: Read> SourceCountRead for Decompressor<'a, S> {
fn source_read_count(&self) -> u64 {
match &self.decoder {
Decoder::Raw(stream) => stream.read_count(),
Decoder::DeflateRaw(stream) => stream.get_ref().read_count(),
Decoder::DeflateZlib(stream) => stream.get_ref().read_count(),
Decoder::Gzip(stream) => stream.get_ref().read_count(),
Decoder::Brotli(stream) => stream.get_ref().read_count(),
Decoder::Zstd(stream) => stream.get_ref().read_count(),
}
}
}
enum Encoder<'a, S: Write> {
Raw(S),
DeflateRaw(DeflateEncoder<S>),
DeflateZlib(ZlibEncoder<S>),
Gzip(GzEncoder<S>),
Brotli(BrotliEncoder<S>),
Zstd(ZstdEncoder<'a, S>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionLevel {
Fast,
CodecDefault,
Optimal,
High,
}
impl Default for CompressionLevel {
fn default() -> Self {
Self::Optimal
}
}
impl CompressionLevel {
fn get_int_for_format(&self, format: CompressionFormat) -> i32 {
match format {
CompressionFormat::Raw => 0,
CompressionFormat::Gzip
| CompressionFormat::DeflateRaw
| CompressionFormat::DeflateZlib => match self {
CompressionLevel::Fast => 1,
CompressionLevel::CodecDefault => 6,
CompressionLevel::Optimal => 9,
CompressionLevel::High => 9,
},
CompressionFormat::Brotli => match self {
CompressionLevel::Fast => 1,
CompressionLevel::CodecDefault => 11,
CompressionLevel::Optimal => 5,
CompressionLevel::High => 11,
},
CompressionFormat::Zstd => match self {
CompressionLevel::Fast => 1,
CompressionLevel::CodecDefault => 3,
CompressionLevel::Optimal => 3,
CompressionLevel::High => 19,
},
}
}
}
pub struct Compressor<'a, S: Write> {
encoder: Encoder<'a, S>,
}
impl<'a, S: Write> Compressor<'a, S> {
pub fn new(
stream: S,
format: CompressionFormat,
level: CompressionLevel,
) -> std::io::Result<Self> {
let encoder = match format {
CompressionFormat::Raw => Encoder::Raw(stream),
CompressionFormat::DeflateRaw => Encoder::DeflateRaw(DeflateEncoder::new(
stream,
GzCompression::new(level.get_int_for_format(format) as u32),
)),
CompressionFormat::DeflateZlib => Encoder::DeflateZlib(ZlibEncoder::new(
stream,
GzCompression::new(level.get_int_for_format(format) as u32),
)),
CompressionFormat::Gzip => Encoder::Gzip(GzEncoder::new(
stream,
GzCompression::new(level.get_int_for_format(format) as u32),
)),
CompressionFormat::Brotli => Encoder::Brotli(BrotliEncoder::new(
stream,
4096,
level.get_int_for_format(format) as u32,
22,
)),
CompressionFormat::Zstd => {
Encoder::Zstd(ZstdEncoder::new(stream, level.get_int_for_format(format))?)
}
};
Ok(Self { encoder })
}
pub fn get_ref(&self) -> &S {
match &self.encoder {
Encoder::Raw(stream) => stream,
Encoder::DeflateRaw(stream) => stream.get_ref(),
Encoder::DeflateZlib(stream) => stream.get_ref(),
Encoder::Gzip(stream) => stream.get_ref(),
Encoder::Brotli(stream) => stream.get_ref(),
Encoder::Zstd(stream) => stream.get_ref(),
}
}
pub fn get_mut(&mut self) -> &mut S {
match &mut self.encoder {
Encoder::Raw(stream) => stream,
Encoder::DeflateRaw(stream) => stream.get_mut(),
Encoder::DeflateZlib(stream) => stream.get_mut(),
Encoder::Gzip(stream) => stream.get_mut(),
Encoder::Brotli(stream) => stream.get_mut(),
Encoder::Zstd(stream) => stream.get_mut(),
}
}
pub fn finish(self) -> std::io::Result<S> {
match self.encoder {
Encoder::Raw(stream) => Ok(stream),
Encoder::DeflateRaw(stream) => stream.finish(),
Encoder::DeflateZlib(stream) => stream.finish(),
Encoder::Gzip(stream) => stream.finish(),
Encoder::Brotli(mut stream) => {
stream.flush()?;
Ok(stream.into_inner())
}
Encoder::Zstd(stream) => stream.finish(),
}
}
}
impl<'a, S: Write> Write for Compressor<'a, S> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match &mut self.encoder {
Encoder::Raw(stream) => stream.write(buf),
Encoder::DeflateRaw(stream) => stream.write(buf),
Encoder::DeflateZlib(stream) => stream.write(buf),
Encoder::Gzip(stream) => stream.write(buf),
Encoder::Brotli(stream) => stream.write(buf),
Encoder::Zstd(stream) => stream.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match &mut self.encoder {
Encoder::Raw(stream) => stream.flush(),
Encoder::DeflateRaw(stream) => stream.flush(),
Encoder::DeflateZlib(stream) => stream.flush(),
Encoder::Gzip(stream) => stream.flush(),
Encoder::Brotli(stream) => stream.flush(),
Encoder::Zstd(stream) => stream.flush(),
}
}
}