use crate::{codec::Encode, util::PartialBuffer};
use std::io;
use flate2::{Compression, Crc};
#[derive(Debug)]
enum State {
Header(PartialBuffer<Vec<u8>>),
Encoding,
Footer(PartialBuffer<Vec<u8>>),
Done,
}
#[derive(Debug)]
pub struct GzipEncoder {
inner: crate::codec::FlateEncoder,
crc: Crc,
state: State,
}
fn header(level: Compression) -> Vec<u8> {
let level_byte = if level.level() >= Compression::best().level() {
0x02
} else if level.level() <= Compression::fast().level() {
0x04
} else {
0x00
};
vec![0x1f, 0x8b, 0x08, 0, 0, 0, 0, 0, level_byte, 0xff]
}
impl GzipEncoder {
pub(crate) fn new(level: Compression) -> Self {
Self {
inner: crate::codec::FlateEncoder::new(level, false),
crc: Crc::new(),
state: State::Header(header(level).into()),
}
}
fn footer(&mut self) -> Vec<u8> {
let mut output = Vec::with_capacity(8);
output.extend(&self.crc.sum().to_le_bytes());
output.extend(&self.crc.amount().to_le_bytes());
output
}
}
impl Encode for GzipEncoder {
fn encode(
&mut self,
input: &mut PartialBuffer<impl AsRef<[u8]>>,
output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> io::Result<()> {
loop {
match &mut self.state {
State::Header(header) => {
output.copy_unwritten_from(&mut *header);
if header.unwritten().is_empty() {
self.state = State::Encoding;
}
}
State::Encoding => {
let prior_written = input.written().len();
self.inner.encode(input, output)?;
self.crc.update(&input.written()[prior_written..]);
}
State::Footer(_) | State::Done => {
return Err(io::Error::new(
io::ErrorKind::Other,
"encode after complete",
));
}
};
if input.unwritten().is_empty() || output.unwritten().is_empty() {
return Ok(());
}
}
}
fn flush(
&mut self,
output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> io::Result<bool> {
loop {
let done = match &mut self.state {
State::Header(header) => {
output.copy_unwritten_from(&mut *header);
if header.unwritten().is_empty() {
self.state = State::Encoding;
}
false
}
State::Encoding => self.inner.flush(output)?,
State::Footer(footer) => {
output.copy_unwritten_from(&mut *footer);
if footer.unwritten().is_empty() {
self.state = State::Done;
true
} else {
false
}
}
State::Done => true,
};
if done {
return Ok(true);
}
if output.unwritten().is_empty() {
return Ok(false);
}
}
}
fn finish(
&mut self,
output: &mut PartialBuffer<impl AsRef<[u8]> + AsMut<[u8]>>,
) -> io::Result<bool> {
loop {
match &mut self.state {
State::Header(header) => {
output.copy_unwritten_from(&mut *header);
if header.unwritten().is_empty() {
self.state = State::Encoding;
}
}
State::Encoding => {
if self.inner.finish(output)? {
self.state = State::Footer(self.footer().into());
}
}
State::Footer(footer) => {
output.copy_unwritten_from(&mut *footer);
if footer.unwritten().is_empty() {
self.state = State::Done;
}
}
State::Done => {}
};
if let State::Done = self.state {
return Ok(true);
}
if output.unwritten().is_empty() {
return Ok(false);
}
}
}
}