use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::compressor::core::dispatch::{self, HqInput, Kernels};
use fearless_simd::Level;
use super::h10::BinaryTreeMatcher;
use super::metablock::MetaBlockBuilder;
use super::params::HqParams;
use super::zopfli::{ZopfliState, ZopfliWorkspace};
use crate::compressor::core::rfc9841::context::SharedContextInner;
use crate::compressor::{BrotliCompressError, BrotliResult, CompressParams};
use crate::shared::bits::{BYTE_PADDING_SLACK, BitWriter, inject_byte_padding};
use crate::shared::bitstream::{MetaBlockWriter, store_uncompressed_meta_block};
use crate::shared::command::{Command, CommandExtension};
use crate::shared::constants::{OUTPUT_RESERVE_CONST, OUTPUT_SLACK};
use crate::shared::distance::DistanceParams;
use crate::shared::format::ContextMode;
use crate::shared::histogram::{HistogramLiteral, bits_entropy};
use crate::shared::metablock::{MetaBlockSplit, optimize_histograms};
use crate::shared::ringbuffer::{BlockSpan, RingBuffer, Window, wrap_position};
const SAMPLE_RATE: u32 = 13;
const MIN_ENTROPY: f64 = 7.92;
const LITERAL_FRACTION: f64 = 0.99;
pub(crate) struct HqEncoder {
kernels: Box<dyn Kernels>,
params: HqParams,
ringbuffer: RingBuffer,
matcher: BinaryTreeMatcher,
is_prepared: bool,
input_pos: u64,
last_processed_pos: u64,
last_flush_pos: u64,
commands: Vec<Command>,
references: ZopfliState,
workspace: ZopfliWorkspace,
builder: MetaBlockBuilder,
metablock: MetaBlockSplit,
saved_dist_cache: [i32; 4],
prev_byte: u8,
prev_byte2: u8,
last_bytes: u16,
last_bytes_bits: u32,
is_last_block_emitted: bool,
finished: bool,
storage: Vec<u8>,
writer: MetaBlockWriter,
output_len: usize,
}
impl HqEncoder {
#[cfg(not(feature = "no_std"))]
pub(crate) fn select_fragment_kernels(&mut self, level: Level) {
self.kernels = dispatch::select_independent(level);
}
#[cfg(not(feature = "no_std"))]
pub(crate) fn begin_fragment(&mut self, prefix: &[u8]) -> BrotliResult<()> {
self.last_bytes = 0;
self.last_bytes_bits = 0;
self.flush_block(prefix, None)?;
Ok(())
}
#[cfg(not(feature = "no_std"))]
pub(crate) const fn fragment_aligned(&self) -> bool {
self.last_bytes_bits == 0
}
pub(crate) fn new(level: Level, params: &CompressParams) -> BrotliResult<Self> {
let resolved = HqParams::new(params)?;
let (last_bytes, last_bytes_bits) = resolved.window.header();
#[cfg(feature = "experimental")]
let (last_bytes, last_bytes_bits) = if resolved.stream_offset != 0 {
(0, 0)
} else {
(last_bytes, last_bytes_bits)
};
let references = ZopfliState::default();
#[cfg(feature = "experimental")]
let references = {
let mut references = references;
if resolved.stream_offset != 0 {
references.dist_cache[..4].fill(-16);
}
references
};
Ok(Self {
kernels: dispatch::select(level),
params: resolved,
ringbuffer: RingBuffer::new(resolved.rb_bits(), resolved.lgblock),
matcher: BinaryTreeMatcher::new(resolved.lgwin),
is_prepared: false,
input_pos: 0,
last_processed_pos: 0,
last_flush_pos: 0,
commands: Vec::new(),
workspace: ZopfliWorkspace::new(resolved.dist.alphabet_size_limit as usize),
builder: MetaBlockBuilder::default(),
metablock: MetaBlockSplit::default(),
saved_dist_cache: references.dist_cache,
references,
prev_byte: 0,
prev_byte2: 0,
last_bytes,
last_bytes_bits,
is_last_block_emitted: false,
finished: false,
storage: Vec::new(),
writer: MetaBlockWriter::default(),
output_len: 0,
})
}
pub(crate) const fn block_size_limit(&self) -> usize {
self.params.input_block_size()
}
#[cfg(test)]
pub(crate) const fn is_finished(&self) -> bool {
self.finished
}
pub(crate) const fn params(&self) -> &HqParams {
&self.params
}
pub(crate) fn reset(&mut self) {
let (last_bytes, last_bytes_bits) = self.params.window.header();
#[cfg(feature = "experimental")]
let (last_bytes, last_bytes_bits) = if self.params.stream_offset != 0 {
(0, 0)
} else {
(last_bytes, last_bytes_bits)
};
self.ringbuffer.reset();
self.is_prepared = false;
self.input_pos = 0;
self.last_processed_pos = 0;
self.last_flush_pos = 0;
self.commands.clear();
self.references = ZopfliState::default();
#[cfg(feature = "experimental")]
if self.params.stream_offset != 0 {
self.references.dist_cache[..4].fill(-16);
}
self.saved_dist_cache = self.references.dist_cache;
self.prev_byte = 0;
self.prev_byte2 = 0;
self.last_bytes = last_bytes;
self.last_bytes_bits = last_bytes_bits;
self.is_last_block_emitted = false;
self.finished = false;
self.output_len = 0;
}
#[cfg_attr(all(feature = "hotpath", not(feature = "no_std")), hotpath::measure)]
#[cfg(test)]
pub(crate) fn encode_block(&mut self, input: &[u8], is_last: bool) -> BrotliResult<&[u8]> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit());
self.encode_block_with(input, is_last, None)
}
#[cfg_attr(all(feature = "hotpath", not(feature = "no_std")), hotpath::measure)]
pub(crate) fn encode_block_with(
&mut self,
input: &[u8],
is_last: bool,
attached: Option<&SharedContextInner>,
) -> BrotliResult<&[u8]> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit());
self.copy_input_to_ring_buffer(input);
self.encode_data(is_last, false, attached)?;
match self.storage.get(..self.output_len) {
Some(output) => Ok(output),
None => Err(BrotliCompressError::BufferOverflow),
}
}
#[cfg_attr(all(feature = "hotpath", not(feature = "no_std")), hotpath::measure)]
pub(crate) fn flush_block(
&mut self,
input: &[u8],
attached: Option<&SharedContextInner>,
) -> BrotliResult<&[u8]> {
debug_assert!(!self.finished);
debug_assert!(input.len() <= self.block_size_limit());
self.copy_input_to_ring_buffer(input);
self.encode_data(false, true, attached)?;
self.reserve_storage(0)?;
let padded = match self.storage.get_mut(self.output_len..) {
Some(tail) if tail.len() >= BYTE_PADDING_SLACK => {
inject_byte_padding(&mut self.last_bytes, &mut self.last_bytes_bits, tail)
}
_ => return Err(BrotliCompressError::BufferOverflow),
};
self.output_len += padded;
match self.storage.get(..self.output_len) {
Some(output) => Ok(output),
None => Err(BrotliCompressError::BufferOverflow),
}
}
pub(crate) fn retained_bytes(&self) -> usize {
self.ringbuffer.retained_bytes()
+ size_of_val(&*self.kernels)
+ self.matcher.retained_bytes()
+ self.workspace.retained_bytes()
+ self.commands.capacity() * size_of::<Command>()
+ self.storage.capacity()
+ self.writer.retained_bytes()
+ self.builder.retained_bytes()
+ self.metablock.retained_bytes()
}
fn copy_input_to_ring_buffer(&mut self, input: &[u8]) {
if input.is_empty() {
return;
}
self.ringbuffer.write(input);
self.input_pos += input.len() as u64;
self.ringbuffer.clear_margin();
}
fn update_last_processed_pos(&mut self) -> bool {
let wrapped_last = wrap_position(self.last_processed_pos);
let wrapped_input = wrap_position(self.input_pos);
self.last_processed_pos = self.input_pos;
wrapped_input < wrapped_last
}
fn reserve_storage(&mut self, size: usize) -> BrotliResult<()> {
let Some(reserve) = size
.checked_mul(2)
.and_then(|doubled| doubled.checked_add(OUTPUT_RESERVE_CONST))
.and_then(|reserve| reserve.checked_add(OUTPUT_SLACK))
else {
return Err(BrotliCompressError::BufferOverflow);
};
if self.storage.len() < reserve {
self.storage = vec![0u8; reserve];
}
Ok(())
}
fn seed_storage_with_the_partial_byte(&mut self) {
if let Some(head) = self.storage.get_mut(..2) {
head[0] = self.last_bytes as u8;
head[1] = (self.last_bytes >> 8) as u8;
}
}
fn emit_empty_stream(&mut self) -> BrotliResult<()> {
self.reserve_storage(0)?;
self.last_bytes |= 3u16 << self.last_bytes_bits;
self.last_bytes_bits += 2;
self.seed_storage_with_the_partial_byte();
self.output_len = ((self.last_bytes_bits + 7) >> 3) as usize;
self.finished = true;
Ok(())
}
fn create_references(&mut self, span: BlockSpan, attached: Option<&SharedContextInner>) {
let Self {
kernels,
params,
ringbuffer,
matcher,
workspace,
references,
commands,
..
} = self;
let data = ringbuffer.buffer();
let mask = ringbuffer.mask();
kernels.hq(HqInput {
matcher,
params,
window: Window { data, mask },
span,
attached,
references,
workspace,
commands,
});
}
fn encode_data(
&mut self,
is_last: bool,
force_flush: bool,
attached: Option<&SharedContextInner>,
) -> BrotliResult<()> {
self.output_len = 0;
let delta = self.input_pos - self.last_processed_pos;
let mut span = BlockSpan {
position: wrap_position(self.last_processed_pos),
bytes: delta as u32,
};
if delta == 0 {
if !self.ringbuffer.is_allocated() {
if is_last {
return self.emit_empty_stream();
}
return Ok(());
}
if !is_last && !force_flush {
return Ok(());
}
}
if self.is_last_block_emitted {
return Err(BrotliCompressError::BufferOverflow);
}
if is_last {
self.is_last_block_emitted = true;
}
let needed = self.commands.len() + span.bytes as usize / 2 + 1;
if self.commands.capacity() < needed {
self.commands
.reserve(needed + span.bytes as usize / 4 + 16 - self.commands.len());
}
self.prepare_matcher(span.position as usize, span.bytes as usize, is_last);
let context_mode = {
let data = self.ringbuffer.buffer();
let mask = self.ringbuffer.mask();
self.params.choose_context_mode(
data,
wrap_position(self.last_flush_pos) as usize,
mask,
(self.input_pos - self.last_flush_pos) as usize,
)
};
if !self.commands.is_empty() && self.references.last_insert_len == 0 {
let Self {
kernels,
params,
ringbuffer,
references,
commands,
last_processed_pos,
..
} = self;
if let Some(command) = commands.last_mut() {
kernels.extend(CommandExtension {
command,
lgwin: params.lgwin,
dist: ¶ms.dist,
last_distance: references.dist_cache[0],
window: Window {
data: ringbuffer.buffer(),
mask: ringbuffer.mask(),
},
last_processed_pos: *last_processed_pos,
attached,
span: &mut span,
});
}
}
#[cfg(feature = "experimental")]
{
self.params.dictionary_context_mode = context_mode;
}
self.create_references(span, attached);
#[cfg(feature = "experimental")]
{
self.params.dictionary_context_mode = ContextMode::Utf8;
}
{
let max_length = self.params.max_metablock_size();
let max_literals = max_length / 8;
let max_commands = max_length / 8;
let processed_bytes = (self.input_pos - self.last_flush_pos) as usize;
let next_input_fits_metablock =
processed_bytes + self.params.input_block_size() <= max_length;
if !is_last
&& !force_flush
&& next_input_fits_metablock
&& self.references.num_literals < max_literals
&& self.commands.len() < max_commands
{
if self.update_last_processed_pos() {
self.is_prepared = false;
}
return Ok(());
}
}
if self.references.last_insert_len > 0 {
self.commands
.push(Command::insert_only(self.references.last_insert_len));
self.references.num_literals += self.references.last_insert_len;
self.references.last_insert_len = 0;
}
if !is_last && self.input_pos == self.last_flush_pos {
return Ok(());
}
debug_assert!(self.input_pos >= self.last_flush_pos);
let metablock_size = (self.input_pos - self.last_flush_pos) as usize;
self.reserve_storage(metablock_size)?;
self.seed_storage_with_the_partial_byte();
let position = self.write_meta_block(metablock_size, is_last, context_mode)?;
let complete = position >> 3;
self.last_bytes = u16::from(self.storage.get(complete).copied().unwrap_or(0));
self.last_bytes_bits = (position & 7) as u32;
self.last_flush_pos = self.input_pos;
if self.update_last_processed_pos() {
self.is_prepared = false;
}
let mask = self.ringbuffer.mask();
if self.last_flush_pos > 0 {
self.prev_byte = self
.ringbuffer
.buffer()
.get(((self.last_flush_pos as u32).wrapping_sub(1) as usize) & mask)
.copied()
.unwrap_or(0);
}
if self.last_flush_pos > 1 {
self.prev_byte2 = self
.ringbuffer
.buffer()
.get(((self.last_flush_pos as u32).wrapping_sub(2) as usize) & mask)
.copied()
.unwrap_or(0);
}
self.commands.clear();
self.references.num_literals = 0;
self.saved_dist_cache = self.references.dist_cache;
self.output_len = complete;
self.finished = is_last;
Ok(())
}
fn prepare_matcher(&mut self, position: usize, input_size: usize, is_last: bool) {
let Self {
kernels,
ringbuffer,
matcher,
is_prepared,
..
} = self;
let data = ringbuffer.buffer();
let mask = ringbuffer.mask();
if !*is_prepared {
matcher.prepare(position == 0 && is_last, input_size);
*is_prepared = true;
}
kernels.stitch(matcher, input_size, position, Window { data, mask });
}
fn write_meta_block(
&mut self,
bytes: usize,
is_last: bool,
context_mode: ContextMode,
) -> BrotliResult<usize> {
let wrapped_last_flush_pos = wrap_position(self.last_flush_pos) as usize;
let Self {
params,
ringbuffer,
commands,
references,
builder,
saved_dist_cache,
prev_byte,
prev_byte2,
last_bytes_bits,
storage,
writer,
..
} = self;
let data = ringbuffer.buffer();
let mask = ringbuffer.mask();
let mut w = BitWriter::new(storage, *last_bytes_bits as usize);
if bytes == 0 {
w.write(2, 3);
w.align();
let position = w.position();
if w.overflowed() {
return Err(BrotliCompressError::BufferOverflow);
}
return Ok(position);
}
if !should_compress(
data,
mask,
self.last_flush_pos,
bytes,
references.num_literals,
commands.len(),
) {
references.dist_cache = *saved_dist_cache;
store_uncompressed_meta_block(
is_last,
data,
wrapped_last_flush_pos,
mask,
bytes,
&mut w,
);
let position = w.position();
if w.overflowed() {
return Err(BrotliCompressError::BufferOverflow);
}
return Ok(position);
}
let saved_last_bytes = u16::from(w.byte(1)) << 8 | u16::from(w.byte(0));
let saved_last_bytes_bits = *last_bytes_bits as usize;
let mut block_dist: DistanceParams = params.dist;
let mb = &mut self.metablock;
mb.clear();
builder.build(
&*self.kernels,
data,
wrapped_last_flush_pos,
mask,
params,
*prev_byte,
*prev_byte2,
commands,
context_mode,
&mut block_dist,
mb,
);
optimize_histograms(block_dist.alphabet_size_limit as usize, mb);
writer.store_meta_block(
data,
wrapped_last_flush_pos,
bytes,
mask,
*prev_byte,
*prev_byte2,
is_last,
context_mode,
&block_dist,
commands,
mb,
&mut w,
);
if bytes + 4 < (w.position() >> 3) {
references.dist_cache = *saved_dist_cache;
w.rewind(saved_last_bytes_bits);
w.set_byte(0, saved_last_bytes as u8);
w.set_byte(1, (saved_last_bytes >> 8) as u8);
store_uncompressed_meta_block(
is_last,
data,
wrapped_last_flush_pos,
mask,
bytes,
&mut w,
);
}
let position = w.position();
if w.overflowed() {
return Err(BrotliCompressError::BufferOverflow);
}
Ok(position)
}
}
fn should_compress(
data: &[u8],
mask: usize,
last_flush_pos: u64,
bytes: usize,
num_literals: usize,
num_commands: usize,
) -> bool {
if bytes <= 2 {
return false;
}
if num_commands >= (bytes >> 8) + 2 {
return true;
}
if num_literals as f64 <= LITERAL_FRACTION * bytes as f64 {
return true;
}
let mut literal_histo = HistogramLiteral::default();
let bit_cost_threshold = bytes as f64 * MIN_ENTROPY / f64::from(SAMPLE_RATE);
let samples = bytes.div_ceil(SAMPLE_RATE as usize);
let mut pos = last_flush_pos as u32;
for _ in 0..samples {
literal_histo.add(usize::from(
data.get(pos as usize & mask).copied().unwrap_or(0),
));
pos = pos.wrapping_add(SAMPLE_RATE);
}
bits_entropy(&literal_histo.data) <= bit_cost_threshold
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compressor::{QualityLevel, WindowBits};
fn encoder(quality: QualityLevel) -> HqEncoder {
let params = CompressParams::new(quality, WindowBits::DEFAULT);
HqEncoder::new(Level::try_detect().unwrap_or_else(Level::baseline), ¶ms)
.expect("supported quality")
}
fn compress(quality: QualityLevel, data: &[u8]) -> Vec<u8> {
compress_with(
Level::try_detect().unwrap_or_else(Level::baseline),
quality,
data,
)
}
fn compress_with(level: Level, quality: QualityLevel, data: &[u8]) -> Vec<u8> {
let params = CompressParams::new(quality, WindowBits::DEFAULT);
let mut encoder = HqEncoder::new(level, ¶ms).expect("supported quality");
let limit = encoder.block_size_limit();
let mut out = Vec::new();
let mut offset = 0usize;
loop {
let take = (data.len() - offset).min(limit);
let is_last = offset + take == data.len();
out.extend_from_slice(
encoder
.encode_block(&data[offset..offset + take], is_last)
.expect("encoding failed"),
);
offset += take;
if is_last {
break;
}
}
out
}
#[test]
fn the_window_header_matches_the_reference_encoding() {
let header = |lgwin| {
HqParams::new(&CompressParams::new(
QualityLevel::Q11,
WindowBits::standard(lgwin).expect("a legal window"),
))
.expect("a supported quality")
.window
.header()
};
assert_eq!(header(16), (0, 1));
assert_eq!(header(17), (1, 7));
assert_eq!(header(22), (11, 4));
assert_eq!(header(10), (0x21, 7));
}
#[test]
fn a_tiny_block_is_never_compressed() {
assert!(!should_compress(&[1, 2], usize::MAX, 0, 2, 2, 0));
}
#[test]
fn random_literals_are_stored_uncompressed() {
let mut rng = 0x1234_5678u64;
let data: Vec<u8> = (0..200_000)
.map(|_| {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
(rng >> 24) as u8
})
.collect();
assert!(!should_compress(
&data,
usize::MAX,
0,
data.len(),
data.len(),
1
));
}
#[test]
fn both_qualities_produce_a_non_empty_stream() {
for quality in [QualityLevel::Q10, QualityLevel::Q11] {
let stream = compress(quality, b"hello hello hello hello hello hello");
assert!(!stream.is_empty(), "quality {quality:?} produced nothing");
}
}
#[test]
fn an_empty_stream_is_two_bits() {
for quality in [QualityLevel::Q10, QualityLevel::Q11] {
let mut encoder = encoder(quality);
let stream = encoder.encode_block(&[], true).expect("encoding failed");
assert!(!stream.is_empty());
assert!(encoder.is_finished());
}
}
#[test]
fn the_block_size_limit_follows_the_window() {
assert_eq!(encoder(QualityLevel::Q10).block_size_limit(), 1 << 18);
assert_eq!(encoder(QualityLevel::Q11).block_size_limit(), 1 << 18);
}
#[test]
fn every_backend_produces_the_same_stream() {
let data: Vec<u8> = (0..120_000u32).map(|i| (i * 7 % 253) as u8).collect();
for quality in [QualityLevel::Q10, QualityLevel::Q11] {
let mut streams = Vec::new();
for level in [
Level::try_detect().unwrap_or_else(Level::baseline),
Level::baseline(),
Level::fallback(),
] {
streams.push(compress_with(level, quality, &data));
}
assert!(
streams.windows(2).all(|pair| pair[0] == pair[1]),
"quality {quality:?} differed between backends"
);
}
}
fn c_compress(quality: i32, lgwin: i32, input: &[u8]) -> Vec<u8> {
let capacity = unsafe { google_brotli_ffi::BrotliEncoderMaxCompressedSize(input.len()) }
.max(64)
+ 1024;
let mut output = vec![0u8; capacity];
let mut size = output.len();
let ok = unsafe {
google_brotli_ffi::BrotliEncoderCompress(
quality,
lgwin,
google_brotli_ffi::BROTLI_DEFAULT_MODE,
input.len(),
input.as_ptr(),
&raw mut size,
output.as_mut_ptr(),
)
};
assert_eq!(ok, google_brotli_ffi::BROTLI_TRUE, "the C encoder failed");
output.truncate(size);
output
}
const COLLISION_REGRESSION: &[u8] = include_bytes!("collision_regression.bin");
#[test]
fn the_collision_regression_matches_the_c_encoder() {
for quality in [QualityLevel::Q10, QualityLevel::Q11] {
let expected = c_compress(usize::from(quality) as i32, 22, COLLISION_REGRESSION);
let actual = compress(quality, COLLISION_REGRESSION);
assert_eq!(
actual,
expected,
"quality {quality:?}: {} bytes against {}",
actual.len(),
expected.len()
);
}
}
#[test]
fn a_collision_pattern_matches_the_c_encoder() {
let mut rng = 0x243F_6A88_85A3_08D3u64;
let mut data = Vec::new();
while data.len() < 4000 {
data.extend_from_slice(b"AAAAA");
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
data.push((rng >> 24) as u8);
}
for len in (1800..2000).chain([3000, 4000]) {
let prefix = &data[..len];
for quality in [QualityLevel::Q10, QualityLevel::Q11] {
let expected = c_compress(usize::from(quality) as i32, 22, prefix);
let actual = compress(quality, prefix);
assert_eq!(
actual,
expected,
"length {len}, quality {quality:?}: {} bytes against {}",
actual.len(),
expected.len()
);
}
}
}
#[test]
fn quality_eleven_compresses_at_least_as_well_as_quality_ten() {
let mut data = Vec::new();
while data.len() < 200_000 {
data.extend_from_slice(
b"The quick brown fox jumps over the lazy dog. Pack my box with five dozen jugs. ",
);
}
let ten = compress(QualityLevel::Q10, &data);
let eleven = compress(QualityLevel::Q11, &data);
assert!(
eleven.len() <= ten.len(),
"quality eleven produced {} bytes against quality ten's {}",
eleven.len(),
ten.len()
);
}
}