use super::block::MAX_BLOCK_LENGTH;
use super::parse::{Params, Parser, Strategy, Tables, LOOKAHEAD};
use super::tables::WINDOW;
use super::write::Writer;
use super::{Flush, Level};
use crate::Format;
pub(super) const MIN_BUF_LEN: usize = WINDOW + MAX_BLOCK_LENGTH + LOOKAHEAD + 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Stage {
Header,
Body,
Marker {
full: bool,
},
Trailer,
Finishing,
Done,
}
macro_rules! encoder {
($(#[$attr:meta])* pub struct Encoder { $($fields:tt)* }) => {
#[cfg(feature = "alloc")]
$(#[$attr])*
pub struct Encoder<T = alloc::boxed::Box<[u16]>> { $($fields)* }
#[cfg(not(feature = "alloc"))]
$(#[$attr])*
pub struct Encoder<T> { $($fields)* }
};
}
encoder! {
pub struct Encoder {
s: State,
tables: T,
}
}
struct State {
format: Format,
level: Level,
params: Params,
stage: Stage,
parser: Parser,
w: Writer,
check: u32,
total_in: u64,
total_out: u64,
flushed_at: Option<usize>,
}
impl<T> core::fmt::Debug for Encoder<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Encoder")
.field("format", &self.s.format)
.field("level", &self.s.level)
.field("total_in", &self.s.total_in)
.field("total_out", &self.s.total_out)
.finish_non_exhaustive()
}
}
#[derive(Debug, PartialEq, Eq)]
#[must_use]
#[non_exhaustive]
pub struct Progress<'o> {
pub consumed: usize,
pub output: &'o [u8],
pub done: bool,
pub full: bool,
}
const fn params(level: Level) -> Params {
use Strategy::*;
const ALL: u32 = u32::MAX;
let (strategy, depth, nice, insert, accel) = match level.0 {
0 => (Stored, 0, 0, 0, 0),
1 => (Fast, 1, 32, 0, 12),
2 => (Fast, 1, 32, 16, 4),
3 => (Fast, 2, 32, 32, 4),
4 => (Greedy, 6, 32, 32, 2),
5 => (Lazy, 6, 32, ALL, 0),
6 => (Lazy, 16, 30, ALL, 0),
7 => (Lazy, 24, 48, ALL, 0),
8 => (Lazy, 35, 65, ALL, 0),
_ => (Lazy, 48, 96, ALL, 0),
};
Params {
strategy,
depth,
nice,
insert,
accel,
}
}
#[cfg(feature = "alloc")]
impl Encoder {
#[must_use]
pub fn new(format: Format, level: Level) -> Self {
let tables = alloc::vec![0; super::TABLES_LEN].into_boxed_slice();
Encoder::with_tables(format, level, tables)
}
}
impl<T> Encoder<T> {
#[must_use]
pub const fn with_tables(format: Format, level: Level, tables: T) -> Self {
Encoder {
s: State {
format,
level,
params: params(level),
stage: Stage::Header,
parser: Parser::new(),
w: Writer::new(),
check: initial_check(format),
total_in: 0,
total_out: 0,
flushed_at: None,
},
tables,
}
}
#[must_use]
pub const fn format(&self) -> Format {
self.s.format
}
#[must_use]
pub const fn level(&self) -> Level {
self.s.level
}
#[must_use]
pub fn total_in(&self) -> u64 {
self.s.total_in
}
#[must_use]
pub fn total_out(&self) -> u64 {
self.s.total_out
}
pub fn reset(&mut self) {
self.s.reset();
}
pub fn into_tables(self) -> T {
self.tables
}
}
impl<T: AsMut<[u16]>> Encoder<T> {
pub fn compress<'o>(&mut self, buf: &[u8], out: &'o mut [u8], flush: Flush) -> Progress<'o> {
let mut t = Tables::carve(self.tables.as_mut());
self.s.compress(&mut t, buf, out, flush)
}
pub fn make_room(&mut self, buf: &mut [u8]) -> usize {
let mut t = Tables::carve(self.tables.as_mut());
self.s.make_room(&mut t, buf)
}
}
impl State {
fn reset(&mut self) {
self.stage = Stage::Header;
self.parser.pos = 0;
self.parser.open = false;
self.w.reset();
self.check = initial_check(self.format);
self.total_in = 0;
self.total_out = 0;
self.flushed_at = None;
}
fn compress<'o>(
&mut self,
t: &mut Tables<'_>,
buf: &[u8],
out: &'o mut [u8],
flush: Flush,
) -> Progress<'o> {
if self.stage == Stage::Done {
self.reset();
}
let start = self.parser.pos;
assert!(
start <= buf.len(),
"the buffer is shorter than the input already consumed"
);
let mut op = 0;
let full = self.run(t, buf, out, &mut op, flush);
let consumed = self.parser.pos - start;
self.total_out += op as u64;
Progress {
consumed,
output: &out[..op],
done: self.stage == Stage::Done,
full,
}
}
fn make_room(&mut self, t: &mut Tables<'_>, buf: &mut [u8]) -> usize {
let p = &mut self.parser;
let pos = p.pos;
if !matches!(self.stage, Stage::Body | Stage::Marker { .. }) || pos > buf.len() {
return 0;
}
let pinned = if p.open || !self.w.is_idle() {
p.block.start
} else {
pos
};
let drop = pos.saturating_sub(WINDOW).min(pinned);
if drop == 0 {
return 0;
}
if self.params.strategy != Strategy::Stored {
p.mf.catch_up(&mut t.lz, pos);
p.mf.base -= drop;
}
buf.copy_within(drop.., 0);
p.pos -= drop;
p.block.start -= drop;
self.flushed_at = self.flushed_at.and_then(|f| f.checked_sub(drop));
drop
}
fn run(
&mut self,
t: &mut Tables<'_>,
buf: &[u8],
out: &mut [u8],
op: &mut usize,
flush: Flush,
) -> bool {
loop {
if !self.w.drain(&self.parser.block, t.seqs, buf, out, op) {
return true;
}
match self.stage {
Stage::Header => self.begin_stream(t, buf, flush),
Stage::Body => {
if !self.body(t, buf, flush) {
return false;
}
}
Stage::Marker { full } => {
self.w.stage_marker(false);
let pos = self.parser.pos;
if full {
self.reset_matches(t, pos, None);
}
self.flushed_at = Some(pos);
self.stage = Stage::Body;
}
Stage::Trailer => {
let mut t = [0; 8];
let len = match self.format {
Format::Deflate => 0,
Format::Zlib => {
t[..4].copy_from_slice(&self.check.to_be_bytes());
4
}
Format::Gzip => {
t[..4].copy_from_slice(&self.check.to_le_bytes());
t[4..].copy_from_slice(&(self.total_in as u32).to_le_bytes());
8
}
};
self.w.stage_bytes(&t[..len]);
self.stage = Stage::Finishing;
}
Stage::Finishing => {
self.stage = Stage::Done;
return false;
}
Stage::Done => return false,
}
}
}
fn begin_stream(&mut self, t: &mut Tables<'_>, buf: &[u8], flush: Flush) {
let len = (flush == Flush::Finish).then_some(buf.len());
self.reset_matches(t, 0, len);
let level = self.level.0;
match self.format {
Format::Deflate => {}
Format::Zlib => {
let flevel = match level {
0 | 1 => 0,
2..=5 => 1,
6 => 2,
_ => 3,
};
let header = 0x7800 | flevel << 6;
let header = header + 31 - header % 31;
self.w.stage_bytes(&u16::to_be_bytes(header));
}
Format::Gzip => {
let xfl = match level {
9 => 2,
0 | 1 => 4,
_ => 0,
};
self.w
.stage_bytes(&[0x1F, 0x8B, 8, 0, 0, 0, 0, 0, xfl, 255]);
}
}
self.stage = Stage::Body;
}
fn reset_matches(&mut self, t: &mut Tables<'_>, pos: usize, len: Option<usize>) {
let s = self.params.strategy;
if s != Strategy::Stored {
let ht = if s == Strategy::Fast {
self.params.depth as usize
} else {
0
};
self.parser.mf.reset(&mut t.lz, pos, ht, len);
}
}
fn body(&mut self, t: &mut Tables<'_>, buf: &[u8], flush: Flush) -> bool {
let end = (flush == Flush::Finish).then_some(buf.len());
let p = self.params;
if !self.parser.open {
self.parser.begin_block(p, buf, end);
}
let stop = if flush == Flush::None && p.strategy != Strategy::Stored {
buf.len().saturating_sub(LOOKAHEAD)
} else {
buf.len()
};
let before = self.parser.pos;
let ended = self.parser.parse(t, p, buf, stop.max(before), end);
let pos = self.parser.pos;
self.update_check(&buf[before..pos]);
self.total_in += (pos - before) as u64;
if ended {
self.end_block(t, flush == Flush::Finish && pos == buf.len());
return true;
}
match flush {
Flush::None => false,
Flush::Sync | Flush::Full => {
if self.flushed_at == Some(pos) {
return false;
}
self.end_block(t, false);
self.stage = Stage::Marker {
full: flush == Flush::Full,
};
true
}
Flush::Finish => {
self.end_block(t, true);
true
}
}
}
fn end_block(&mut self, t: &mut Tables<'_>, last: bool) {
let p = &mut self.parser;
p.open = false;
let len = p.pos - p.block.start;
if self.params.strategy == Strategy::Stored {
if len > 0 {
self.w.begin_stored(len, last);
} else if last {
self.w.stage_marker(true);
}
} else if len > 0 || last {
self.w.begin_block(&mut p.block, t.seqs, len, last);
}
if last {
self.stage = Stage::Trailer;
}
}
fn update_check(&mut self, data: &[u8]) {
self.check = match self.format {
Format::Deflate => 0,
Format::Zlib => crate::checksum::adler32(self.check, data),
Format::Gzip => crate::checksum::crc32(self.check, data),
};
}
}
const fn initial_check(format: Format) -> u32 {
match format {
Format::Zlib => 1,
_ => 0,
}
}