| 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); |
| } |
| } |
| } |
| } |