/* Copyright 2013 Google Inc. All Rights Reserved.
Distributed under MIT license .
See file LICENSE for detail or copy at https : //opensource.org/licenses/MIT
*/
/* Implementation of Brotli compressor. */
#include <brotli/encode.h>
#include "../common/constants.h"
#include "../common/context.h"
#include "../common/platform.h"
#include <brotli/shared_dictionary.h>
#include "../common/version.h"
#include "backward_references_hq.h"
#include "backward_references.h"
#include "bit_cost.h"
#include "brotli_bit_stream.h"
#include "command.h"
#include "compound_dictionary.h"
#include "compress_fragment_two_pass.h"
#include "compress_fragment.h"
#include "dictionary_hash.h"
#include "encoder_dict.h"
#include "fast_log.h"
#include "hash.h"
#include "histogram.h"
#include "memory.h"
#include "metablock.h"
#include "params.h"
#include "quality.h"
#include "ringbuffer.h"
#include "state.h"
#include "static_init.h"
#include "utf8_util.h"
#include "write_bits.h"
#if defined (__cplusplus) || defined (c_plusplus)
extern "C" {
#endif
#define COPY_ARRAY(dst, src) memcpy(dst, src, sizeof (src));
static size_t InputBlockSize(BrotliEncoderState* s) {
return (size_t)1 << s->params.lgblock;
}
static uint64_t UnprocessedInputSize(BrotliEncoderState* s) {
return s->input_pos_ - s->last_processed_pos_;
}
static size_t RemainingInputBlockSize(BrotliEncoderState* s) {
const uint64_t delta = UnprocessedInputSize(s);
size_t block_size = InputBlockSize(s);
if (delta >= block_size) return 0 ;
return block_size - (size_t)delta;
}
BROTLI_BOOL BrotliEncoderSetParameter(
BrotliEncoderState* state, BrotliEncoderParameter p, uint32_t value) {
/* Changing parameters on the fly is not implemented yet. */
if (state->is_initialized_) return BROTLI_FALSE;
/* TODO(eustas): Validate/clamp parameters here. */
switch (p) {
case BROTLI_PARAM_MODE:
state->params.mode = (BrotliEncoderMode)value;
return BROTLI_TRUE;
case BROTLI_PARAM_QUALITY:
state->params.quality = (int )value;
return BROTLI_TRUE;
case BROTLI_PARAM_LGWIN:
state->params.lgwin = (int )value;
return BROTLI_TRUE;
case BROTLI_PARAM_LGBLOCK:
state->params.lgblock = (int )value;
return BROTLI_TRUE;
case BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:
if ((value != 0 ) && (value != 1 )) return BROTLI_FALSE;
state->params.disable_literal_context_modeling = TO_BROTLI_BOOL(!!value);
return BROTLI_TRUE;
case BROTLI_PARAM_SIZE_HINT:
state->params.size_hint = value;
return BROTLI_TRUE;
case BROTLI_PARAM_LARGE_WINDOW:
state->params.large_window = TO_BROTLI_BOOL(!!value);
return BROTLI_TRUE;
case BROTLI_PARAM_NPOSTFIX:
state->params.dist.distance_postfix_bits = value;
return BROTLI_TRUE;
case BROTLI_PARAM_NDIRECT:
state->params.dist.num_direct_distance_codes = value;
return BROTLI_TRUE;
case BROTLI_PARAM_STREAM_OFFSET:
if (value > (1 u << 30 )) return BROTLI_FALSE;
state->params.stream_offset = value;
return BROTLI_TRUE;
default : return BROTLI_FALSE;
}
}
/* Wraps 64-bit input position to 32-bit ring-buffer position preserving
"not-a-first-lap" feature. */
static uint32_t WrapPosition(uint64_t position) {
uint32_t result = (uint32_t)position;
uint64_t gb = position >> 30 ;
if (gb > 2 ) {
/* Wrap every 2GiB; The first 3GB are continuous. */
result = (result & ((1 u << 30 ) - 1 )) | ((uint32_t)((gb - 1 ) & 1 ) + 1 ) << 30 ;
}
return result;
}
static uint8_t* GetBrotliStorage(BrotliEncoderState* s, size_t size) {
MemoryManager* m = &s->memory_manager_;
if (s->storage_size_ < size) {
BROTLI_FREE(m, s->storage_);
s->storage_ = BROTLI_ALLOC(m, uint8_t, size);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(s->storage_)) return NULL;
s->storage_size_ = size;
}
return s->storage_;
}
static size_t HashTableSize(size_t max_table_size, size_t input_size) {
size_t htsize = 256 ;
while (htsize < max_table_size && htsize < input_size) {
htsize <<= 1 ;
}
return htsize;
}
static int * GetHashTable(BrotliEncoderState* s, int quality,
size_t input_size, size_t* table_size) {
/* Use smaller hash table when input.size() is smaller, since we
fill the table , incurring O ( hash table size ) overhead for
compression , and if the input is short , we won ' t need that
many hash table entries anyway. */
MemoryManager* m = &s->memory_manager_;
const size_t max_table_size = MaxHashTableSize(quality);
size_t htsize = HashTableSize(max_table_size, input_size);
int * table;
BROTLI_DCHECK(max_table_size >= 256 );
if (quality == FAST_ONE_PASS_COMPRESSION_QUALITY) {
/* Only odd shifts are supported by fast-one-pass. */
if ((htsize & 0 xAAAAA) == 0 ) {
htsize <<= 1 ;
}
}
if (htsize <= sizeof (s->small_table_) / sizeof (s->small_table_[0 ])) {
table = s->small_table_;
} else {
if (htsize > s->large_table_size_) {
s->large_table_size_ = htsize;
BROTLI_FREE(m, s->large_table_);
s->large_table_ = BROTLI_ALLOC(m, int , htsize);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(s->large_table_)) return 0 ;
}
table = s->large_table_;
}
*table_size = htsize;
memset(table, 0 , htsize * sizeof (*table));
return table;
}
static void EncodeWindowBits(int lgwin, BROTLI_BOOL large_window,
uint16_t* last_bytes, uint8_t* last_bytes_bits) {
if (large_window) {
*last_bytes = (uint16_t)(((lgwin & 0 x3F) << 8 ) | 0 x11);
*last_bytes_bits = 14 ;
} else {
if (lgwin == 16 ) {
*last_bytes = 0 ;
*last_bytes_bits = 1 ;
} else if (lgwin == 17 ) {
*last_bytes = 1 ;
*last_bytes_bits = 7 ;
} else if (lgwin > 17 ) {
*last_bytes = (uint16_t)(((lgwin - 17 ) << 1 ) | 0 x01);
*last_bytes_bits = 4 ;
} else {
*last_bytes = (uint16_t)(((lgwin - 8 ) << 4 ) | 0 x01);
*last_bytes_bits = 7 ;
}
}
}
/* TODO(eustas): move to compress_fragment.c? */
/* Initializes the command and distance prefix codes for the first block. */
static void InitCommandPrefixCodes(BrotliOnePassArena* s) {
static const BROTLI_MODEL("small" ) uint8_t kDefaultCommandDepths[128 ] = {
0 , 4 , 4 , 5 , 6 , 6 , 7 , 7 , 7 , 7 , 7 , 8 , 8 , 8 , 8 , 8 ,
0 , 0 , 0 , 4 , 4 , 4 , 4 , 4 , 5 , 5 , 6 , 6 , 6 , 6 , 7 , 7 ,
7 , 7 , 10 , 10 , 10 , 10 , 10 , 10 , 0 , 4 , 4 , 5 , 5 , 5 , 6 , 6 ,
7 , 8 , 8 , 9 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 ,
5 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
6 , 6 , 6 , 6 , 6 , 6 , 5 , 5 , 5 , 5 , 5 , 5 , 4 , 4 , 4 , 4 ,
4 , 4 , 4 , 5 , 5 , 5 , 5 , 5 , 5 , 6 , 6 , 7 , 7 , 7 , 8 , 10 ,
12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 , 12 ,
};
static const BROTLI_MODEL("small" ) uint16_t kDefaultCommandBits[128 ] = {
0 , 0 , 8 , 9 , 3 , 35 , 7 , 71 ,
39 , 103 , 23 , 47 , 175 , 111 , 239 , 31 ,
0 , 0 , 0 , 4 , 12 , 2 , 10 , 6 ,
13 , 29 , 11 , 43 , 27 , 59 , 87 , 55 ,
15 , 79 , 319 , 831 , 191 , 703 , 447 , 959 ,
0 , 14 , 1 , 25 , 5 , 21 , 19 , 51 ,
119 , 159 , 95 , 223 , 479 , 991 , 63 , 575 ,
127 , 639 , 383 , 895 , 255 , 767 , 511 , 1023 ,
14 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
27 , 59 , 7 , 39 , 23 , 55 , 30 , 1 , 17 , 9 , 25 , 5 , 0 , 8 , 4 , 12 ,
2 , 10 , 6 , 21 , 13 , 29 , 3 , 19 , 11 , 15 , 47 , 31 , 95 , 63 , 127 , 255 ,
767 , 2815 , 1791 , 3839 , 511 , 2559 , 1535 , 3583 , 1023 , 3071 , 2047 , 4095 ,
};
static const BROTLI_MODEL("small" ) uint8_t kDefaultCommandCode[] = {
0 xff, 0 x77, 0 xd5, 0 xbf, 0 xe7, 0 xde, 0 xea, 0 x9e, 0 x51, 0 x5d, 0 xde, 0 xc6,
0 x70, 0 x57, 0 xbc, 0 x58, 0 x58, 0 x58, 0 xd8, 0 xd8, 0 x58, 0 xd5, 0 xcb, 0 x8c,
0 xea, 0 xe0, 0 xc3, 0 x87, 0 x1f, 0 x83, 0 xc1, 0 x60, 0 x1c, 0 x67, 0 xb2, 0 xaa,
0 x06, 0 x83, 0 xc1, 0 x60, 0 x30, 0 x18, 0 xcc, 0 xa1, 0 xce, 0 x88, 0 x54, 0 x94,
0 x46, 0 xe1, 0 xb0, 0 xd0, 0 x4e, 0 xb2, 0 xf7, 0 x04, 0 x00,
};
static const size_t kDefaultCommandCodeNumBits = 448 ;
COPY_ARRAY(s->cmd_depth, kDefaultCommandDepths);
COPY_ARRAY(s->cmd_bits, kDefaultCommandBits);
/* Initialize the pre-compressed form of the command and distance prefix
codes. */
COPY_ARRAY(s->cmd_code, kDefaultCommandCode);
s->cmd_code_numbits = kDefaultCommandCodeNumBits;
}
/* TODO(eustas): avoid FP calculations. */
static double EstimateEntropy(const uint32_t* population, size_t size) {
size_t total = 0 ;
double result = 0 ;
size_t i;
for (i = 0 ; i < size; ++i) {
uint32_t p = population[i];
total += p;
result += (double )p * FastLog2(p);
}
result = (double )total * FastLog2(total) - result;
return result;
}
/* Decide about the context map based on the ability of the prediction
ability of the previous byte UTF8 - prefix on the next byte . The
prediction ability is calculated as Shannon entropy . Here we need
Shannon entropy instead of ' BrotliBitsEntropy ' since the prefix will be
encoded with the remaining 6 bits of the following byte , and
BrotliBitsEntropy will assume that symbol to be stored alone using Huffman
coding. */
static void ChooseContextMap(int quality,
uint32_t* bigram_histo,
size_t* num_literal_contexts,
const uint32_t** literal_context_map) {
static const BROTLI_MODEL("small" )
uint32_t kStaticContextMapContinuation[64 ] = {
1 , 1 , 2 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
};
static const BROTLI_MODEL("small" )
uint32_t kStaticContextMapSimpleUTF8[64 ] = {
0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
};
uint32_t monogram_histo[3 ] = { 0 };
uint32_t two_prefix_histo[6 ] = { 0 };
size_t total;
size_t i;
double entropy[4 ];
for (i = 0 ; i < 9 ; ++i) {
monogram_histo[i % 3 ] += bigram_histo[i];
two_prefix_histo[i % 6 ] += bigram_histo[i];
}
entropy[1 ] = EstimateEntropy(monogram_histo, 3 );
entropy[2 ] = (EstimateEntropy(two_prefix_histo, 3 ) +
EstimateEntropy(two_prefix_histo + 3 , 3 ));
entropy[3 ] = 0 ;
for (i = 0 ; i < 3 ; ++i) {
entropy[3 ] += EstimateEntropy(bigram_histo + 3 * i, 3 );
}
total = monogram_histo[0 ] + monogram_histo[1 ] + monogram_histo[2 ];
BROTLI_DCHECK(total != 0 );
entropy[0 ] = 1 .0 / (double )total;
entropy[1 ] *= entropy[0 ];
entropy[2 ] *= entropy[0 ];
entropy[3 ] *= entropy[0 ];
if (quality < MIN_QUALITY_FOR_HQ_CONTEXT_MODELING) {
/* 3 context models is a bit slower, don't use it at lower qualities. */
entropy[3 ] = entropy[1 ] * 10 ;
}
/* If expected savings by symbol are less than 0.2 bits, skip the
context modeling -- in exchange for faster decoding speed. */
if (entropy[1 ] - entropy[2 ] < 0 .2 &&
entropy[1 ] - entropy[3 ] < 0 .2 ) {
*num_literal_contexts = 1 ;
} else if (entropy[2 ] - entropy[3 ] < 0 .02 ) {
*num_literal_contexts = 2 ;
*literal_context_map = kStaticContextMapSimpleUTF8;
} else {
*num_literal_contexts = 3 ;
*literal_context_map = kStaticContextMapContinuation;
}
}
/* Decide if we want to use a more complex static context map containing 13
context values , based on the entropy reduction of histograms over the
first 5 bits of literals. */
static BROTLI_BOOL ShouldUseComplexStaticContextMap(const uint8_t* input,
size_t start_pos, size_t length, size_t mask, int quality, size_t size_hint,
size_t* num_literal_contexts, const uint32_t** literal_context_map,
uint32_t* arena) {
static const BROTLI_MODEL("small" )
uint32_t kStaticContextMapComplexUTF8[64 ] = {
11 , 11 , 12 , 12 , /* 0 special */
0 , 0 , 0 , 0 , /* 4 lf */
1 , 1 , 9 , 9 , /* 8 space */
2 , 2 , 2 , 2 , /* !, first after space/lf and after something else. */
1 , 1 , 1 , 1 , /* " */
8 , 3 , 3 , 3 , /* % */
1 , 1 , 1 , 1 , /* ({[ */
2 , 2 , 2 , 2 , /* }]) */
8 , 4 , 4 , 4 , /* :; */
8 , 7 , 4 , 4 , /* . */
8 , 0 , 0 , 0 , /* > */
3 , 3 , 3 , 3 , /* [0..9] */
5 , 5 , 10 , 5 , /* [A-Z] */
5 , 5 , 10 , 5 ,
6 , 6 , 6 , 6 , /* [a-z] */
6 , 6 , 6 , 6 ,
};
BROTLI_UNUSED(quality);
/* Try the more complex static context map only for long data. */
if (size_hint < (1 << 20 )) {
return BROTLI_FALSE;
} else {
const size_t end_pos = start_pos + length;
/* To make entropy calculations faster, we collect histograms
over the 5 most significant bits of literals . One histogram
without context and 13 additional histograms for each context value. */
uint32_t* BROTLI_RESTRICT const combined_histo = arena;
uint32_t* BROTLI_RESTRICT const context_histo = arena + 32 ;
uint32_t total = 0 ;
double entropy[3 ];
size_t i;
ContextLut utf8_lut = BROTLI_CONTEXT_LUT(CONTEXT_UTF8);
memset(arena, 0 , sizeof (arena[0 ]) * 32 * (BROTLI_MAX_STATIC_CONTEXTS + 1 ));
for (; start_pos + 64 <= end_pos; start_pos += 4096 ) {
const size_t stride_end_pos = start_pos + 64 ;
uint8_t prev2 = input[start_pos & mask];
uint8_t prev1 = input[(start_pos + 1 ) & mask];
size_t pos;
/* To make the analysis of the data faster we only examine 64 byte long
strides at every 4kB intervals. */
for (pos = start_pos + 2 ; pos < stride_end_pos; ++pos) {
const uint8_t literal = input[pos & mask];
const uint8_t context = (uint8_t)kStaticContextMapComplexUTF8[
BROTLI_CONTEXT(prev1, prev2, utf8_lut)];
++total;
++combined_histo[literal >> 3 ];
++context_histo[(context << 5 ) + (literal >> 3 )];
prev2 = prev1;
prev1 = literal;
}
}
entropy[1 ] = EstimateEntropy(combined_histo, 32 );
entropy[2 ] = 0 ;
for (i = 0 ; i < BROTLI_MAX_STATIC_CONTEXTS; ++i) {
entropy[2 ] += EstimateEntropy(context_histo + (i << 5 ), 32 );
}
entropy[0 ] = 1 .0 / (double )total;
entropy[1 ] *= entropy[0 ];
entropy[2 ] *= entropy[0 ];
/* The triggering heuristics below were tuned by compressing the individual
files of the silesia corpus . If we skip this kind of context modeling
for not very well compressible input ( i . e . entropy using context modeling
is 60 % of maximal entropy ) or if expected savings by symbol are less
than 0 . 2 bits , then in every case when it triggers , the final compression
ratio is improved . Note however that this heuristics might be too strict
for some cases and could be tuned further. */
if (entropy[2 ] > 3 .0 || entropy[1 ] - entropy[2 ] < 0 .2 ) {
return BROTLI_FALSE;
} else {
*num_literal_contexts = BROTLI_MAX_STATIC_CONTEXTS;
*literal_context_map = kStaticContextMapComplexUTF8;
return BROTLI_TRUE;
}
}
}
static void DecideOverLiteralContextModeling(const uint8_t* input,
size_t start_pos, size_t length, size_t mask, int quality, size_t size_hint,
size_t* num_literal_contexts, const uint32_t** literal_context_map,
uint32_t* arena) {
if (quality < MIN_QUALITY_FOR_CONTEXT_MODELING || length < 64 ) {
return ;
} else if (ShouldUseComplexStaticContextMap(
input, start_pos, length, mask, quality, size_hint,
num_literal_contexts, literal_context_map, arena)) {
/* Context map was already set, nothing else to do. */
} else {
/* Gather bi-gram data of the UTF8 byte prefixes. To make the analysis of
UTF8 data faster we only examine 64 byte long strides at every 4 kB
intervals. */
const size_t end_pos = start_pos + length;
uint32_t* BROTLI_RESTRICT const bigram_prefix_histo = arena;
memset(bigram_prefix_histo, 0 , sizeof (arena[0 ]) * 9 );
for (; start_pos + 64 <= end_pos; start_pos += 4096 ) {
static const int lut[4 ] = { 0 , 0 , 1 , 2 };
const size_t stride_end_pos = start_pos + 64 ;
int prev = lut[input[start_pos & mask] >> 6 ] * 3 ;
size_t pos;
for (pos = start_pos + 1 ; pos < stride_end_pos; ++pos) {
const uint8_t literal = input[pos & mask];
++bigram_prefix_histo[prev + lut[literal >> 6 ]];
prev = lut[literal >> 6 ] * 3 ;
}
}
ChooseContextMap(quality, &bigram_prefix_histo[0 ], num_literal_contexts,
literal_context_map);
}
}
static BROTLI_BOOL ShouldCompress(
const uint8_t* data, const size_t mask, const uint64_t last_flush_pos,
const size_t bytes, const size_t num_literals, const size_t num_commands) {
/* TODO(eustas): find more precise minimal block overhead. */
if (bytes <= 2 ) return BROTLI_FALSE;
if (num_commands < (bytes >> 8 ) + 2 ) {
if ((double )num_literals > 0 .99 * (double )bytes) {
uint32_t literal_histo[256 ] = { 0 };
static const uint32_t kSampleRate = 13 ;
static const double kInvSampleRate = 1 .0 / 13 .0 ;
static const double kMinEntropy = 7 .92 ;
const double bit_cost_threshold =
(double )bytes * kMinEntropy * kInvSampleRate;
size_t t = (bytes + kSampleRate - 1 ) / kSampleRate;
uint32_t pos = (uint32_t)last_flush_pos;
size_t i;
for (i = 0 ; i < t; i++) {
++literal_histo[data[pos & mask]];
pos += kSampleRate;
}
if (BrotliBitsEntropy(literal_histo, 256 ) > bit_cost_threshold) {
return BROTLI_FALSE;
}
}
}
return BROTLI_TRUE;
}
/* Chooses the literal context mode for a metablock */
static ContextType ChooseContextMode(const BrotliEncoderParams* params,
const uint8_t* data, const size_t pos, const size_t mask,
const size_t length) {
/* We only do the computation for the option of something else than
CONTEXT_UTF8 for the highest qualities */
if (params->quality >= MIN_QUALITY_FOR_HQ_BLOCK_SPLITTING &&
!BrotliIsMostlyUTF8(data, pos, mask, length, kMinUTF8Ratio)) {
return CONTEXT_SIGNED;
}
return CONTEXT_UTF8;
}
static void WriteMetaBlockInternal(MemoryManager* m,
const uint8_t* data,
const size_t mask,
const uint64_t last_flush_pos,
const size_t bytes,
const BROTLI_BOOL is_last,
ContextType literal_context_mode,
const BrotliEncoderParams* params,
const uint8_t prev_byte,
const uint8_t prev_byte2,
const size_t num_literals,
const size_t num_commands,
Command* commands,
const int * saved_dist_cache,
int * dist_cache,
size_t* storage_ix,
uint8_t* storage) {
const uint32_t wrapped_last_flush_pos = WrapPosition(last_flush_pos);
uint16_t last_bytes;
uint8_t last_bytes_bits;
ContextLut literal_context_lut = BROTLI_CONTEXT_LUT(literal_context_mode);
BrotliEncoderParams block_params = *params;
if (bytes == 0 ) {
/* Write the ISLAST and ISEMPTY bits. */
BrotliWriteBits(2 , 3 , storage_ix, storage);
*storage_ix = (*storage_ix + 7 u) & ~7 u;
return ;
}
if (!ShouldCompress(data, mask, last_flush_pos, bytes,
num_literals, num_commands)) {
/* Restore the distance cache, as its last update by
CreateBackwardReferences is now unused. */
memcpy(dist_cache, saved_dist_cache, 4 * sizeof (dist_cache[0 ]));
BrotliStoreUncompressedMetaBlock(is_last, data,
wrapped_last_flush_pos, mask, bytes,
storage_ix, storage);
return ;
}
BROTLI_DCHECK(*storage_ix <= 14 );
last_bytes = (uint16_t)((storage[1 ] << 8 ) | storage[0 ]);
last_bytes_bits = (uint8_t)(*storage_ix);
if (params->quality <= MAX_QUALITY_FOR_STATIC_ENTROPY_CODES) {
BrotliStoreMetaBlockFast(m, data, wrapped_last_flush_pos,
bytes, mask, is_last, params,
commands, num_commands,
storage_ix, storage);
if (BROTLI_IS_OOM(m)) return ;
} else if (params->quality < MIN_QUALITY_FOR_BLOCK_SPLIT) {
BrotliStoreMetaBlockTrivial(m, data, wrapped_last_flush_pos,
bytes, mask, is_last, params,
commands, num_commands,
storage_ix, storage);
if (BROTLI_IS_OOM(m)) return ;
} else {
MetaBlockSplit mb;
InitMetaBlockSplit(&mb);
if (params->quality < MIN_QUALITY_FOR_HQ_BLOCK_SPLITTING) {
size_t num_literal_contexts = 1 ;
const uint32_t* literal_context_map = NULL;
if (!params->disable_literal_context_modeling) {
/* TODO(eustas): pull to higher level and reuse. */
uint32_t* arena =
BROTLI_ALLOC(m, uint32_t, 32 * (BROTLI_MAX_STATIC_CONTEXTS + 1 ));
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(arena)) return ;
DecideOverLiteralContextModeling(
data, wrapped_last_flush_pos, bytes, mask, params->quality,
params->size_hint, &num_literal_contexts,
&literal_context_map, arena);
BROTLI_FREE(m, arena);
}
BrotliBuildMetaBlockGreedy(m, data, wrapped_last_flush_pos, mask,
prev_byte, prev_byte2, literal_context_lut, num_literal_contexts,
literal_context_map, commands, num_commands, &mb);
if (BROTLI_IS_OOM(m)) return ;
} else {
BrotliBuildMetaBlock(m, data, wrapped_last_flush_pos, mask, &block_params,
prev_byte, prev_byte2,
commands, num_commands,
literal_context_mode,
&mb);
if (BROTLI_IS_OOM(m)) return ;
}
if (params->quality >= MIN_QUALITY_FOR_OPTIMIZE_HISTOGRAMS) {
/* The number of distance symbols effectively used for distance
histograms . It might be less than distance alphabet size
for "Large Window Brotli" (32-bit). */
BrotliOptimizeHistograms(block_params.dist.alphabet_size_limit, &mb);
}
BrotliStoreMetaBlock(m, data, wrapped_last_flush_pos, bytes, mask,
prev_byte, prev_byte2,
is_last,
&block_params,
literal_context_mode,
commands, num_commands,
&mb,
storage_ix, storage);
if (BROTLI_IS_OOM(m)) return ;
DestroyMetaBlockSplit(m, &mb);
}
if (bytes + 4 < (*storage_ix >> 3 )) {
/* Restore the distance cache and last byte. */
memcpy(dist_cache, saved_dist_cache, 4 * sizeof (dist_cache[0 ]));
storage[0 ] = (uint8_t)last_bytes;
storage[1 ] = (uint8_t)(last_bytes >> 8 );
*storage_ix = last_bytes_bits;
BrotliStoreUncompressedMetaBlock(is_last, data,
wrapped_last_flush_pos, mask,
bytes, storage_ix, storage);
}
}
static void ChooseDistanceParams(BrotliEncoderParams* params) {
uint32_t distance_postfix_bits = 0 ;
uint32_t num_direct_distance_codes = 0 ;
if (params->quality >= MIN_QUALITY_FOR_NONZERO_DISTANCE_PARAMS) {
uint32_t ndirect_msb;
if (params->mode == BROTLI_MODE_FONT) {
distance_postfix_bits = 1 ;
num_direct_distance_codes = 12 ;
} else {
distance_postfix_bits = params->dist.distance_postfix_bits;
num_direct_distance_codes = params->dist.num_direct_distance_codes;
}
ndirect_msb = (num_direct_distance_codes >> distance_postfix_bits) & 0 x0F;
if (distance_postfix_bits > BROTLI_MAX_NPOSTFIX ||
num_direct_distance_codes > BROTLI_MAX_NDIRECT ||
(ndirect_msb << distance_postfix_bits) != num_direct_distance_codes) {
distance_postfix_bits = 0 ;
num_direct_distance_codes = 0 ;
}
}
BrotliInitDistanceParams(¶ms->dist, distance_postfix_bits,
num_direct_distance_codes, params->large_window);
}
static BROTLI_BOOL EnsureInitialized(BrotliEncoderState* s) {
MemoryManager* m = &s->memory_manager_;
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
if (s->is_initialized_) return BROTLI_TRUE;
s->last_bytes_bits_ = 0 ;
s->last_bytes_ = 0 ;
s->flint_ = BROTLI_FLINT_DONE;
s->remaining_metadata_bytes_ = BROTLI_UINT32_MAX;
SanitizeParams(&s->params);
s->params.lgblock = ComputeLgBlock(&s->params);
ChooseDistanceParams(&s->params);
if (s->params.stream_offset != 0 ) {
s->flint_ = BROTLI_FLINT_NEEDS_2_BYTES;
/* Poison the distance cache. -16 +- 3 is still less than zero (invalid). */
s->dist_cache_[0 ] = -16 ;
s->dist_cache_[1 ] = -16 ;
s->dist_cache_[2 ] = -16 ;
s->dist_cache_[3 ] = -16 ;
memcpy(s->saved_dist_cache_, s->dist_cache_, sizeof (s->saved_dist_cache_));
}
RingBufferSetup(&s->params, &s->ringbuffer_);
/* Initialize last byte with stream header. */
{
int lgwin = s->params.lgwin;
if (s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY ||
s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY) {
lgwin = BROTLI_MAX(int , lgwin, 18 );
}
if (s->params.stream_offset == 0 ) {
EncodeWindowBits(lgwin, s->params.large_window,
&s->last_bytes_, &s->last_bytes_bits_);
} else {
/* Bigger values have the same effect, but could cause overflows. */
s->params.stream_offset = BROTLI_MIN(size_t,
s->params.stream_offset, BROTLI_MAX_BACKWARD_LIMIT(lgwin));
}
}
if (s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY) {
s->one_pass_arena_ = BROTLI_ALLOC(m, BrotliOnePassArena, 1 );
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
InitCommandPrefixCodes(s->one_pass_arena_);
} else if (s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY) {
s->two_pass_arena_ = BROTLI_ALLOC(m, BrotliTwoPassArena, 1 );
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
}
s->is_initialized_ = BROTLI_TRUE;
return BROTLI_TRUE;
}
static void BrotliEncoderInitParams(BrotliEncoderParams* params) {
params->mode = BROTLI_DEFAULT_MODE;
params->large_window = BROTLI_FALSE;
params->quality = BROTLI_DEFAULT_QUALITY;
params->lgwin = BROTLI_DEFAULT_WINDOW;
params->lgblock = 0 ;
params->stream_offset = 0 ;
params->size_hint = 0 ;
params->disable_literal_context_modeling = BROTLI_FALSE;
BrotliInitSharedEncoderDictionary(¶ms->dictionary);
params->dist.distance_postfix_bits = 0 ;
params->dist.num_direct_distance_codes = 0 ;
params->dist.alphabet_size_max =
BROTLI_DISTANCE_ALPHABET_SIZE(0 , 0 , BROTLI_MAX_DISTANCE_BITS);
params->dist.alphabet_size_limit = params->dist.alphabet_size_max;
params->dist.max_distance = BROTLI_MAX_DISTANCE;
}
static void BrotliEncoderCleanupParams(MemoryManager* m,
BrotliEncoderParams* params) {
BrotliCleanupSharedEncoderDictionary(m, ¶ms->dictionary);
}
#ifdef BROTLI_REPORTING
/* When BROTLI_REPORTING is defined extra reporting module have to be linked. */
void BrotliEncoderOnStart(const BrotliEncoderState* s);
void BrotliEncoderOnFinish(const BrotliEncoderState* s);
#define BROTLI_ENCODER_ON_START(s) BrotliEncoderOnStart(s);
#define BROTLI_ENCODER_ON_FINISH(s) BrotliEncoderOnFinish(s);
#else
#if !defined (BROTLI_ENCODER_ON_START)
#define BROTLI_ENCODER_ON_START(s) (void )(s);
#endif
#if !defined (BROTLI_ENCODER_ON_FINISH)
#define BROTLI_ENCODER_ON_FINISH(s) (void )(s);
#endif
#endif
static void BrotliEncoderInitState(BrotliEncoderState* s) {
BROTLI_ENCODER_ON_START(s);
BrotliEncoderInitParams(&s->params);
s->input_pos_ = 0 ;
s->num_commands_ = 0 ;
s->num_literals_ = 0 ;
s->last_insert_len_ = 0 ;
s->last_flush_pos_ = 0 ;
s->last_processed_pos_ = 0 ;
s->prev_byte_ = 0 ;
s->prev_byte2_ = 0 ;
s->storage_size_ = 0 ;
s->storage_ = 0 ;
HasherInit(&s->hasher_);
s->large_table_ = NULL;
s->large_table_size_ = 0 ;
s->one_pass_arena_ = NULL;
s->two_pass_arena_ = NULL;
s->command_buf_ = NULL;
s->literal_buf_ = NULL;
s->total_in_ = 0 ;
s->next_out_ = NULL;
s->available_out_ = 0 ;
s->total_out_ = 0 ;
s->stream_state_ = BROTLI_STREAM_PROCESSING;
s->is_last_block_emitted_ = BROTLI_FALSE;
s->is_initialized_ = BROTLI_FALSE;
RingBufferInit(&s->ringbuffer_);
s->commands_ = 0 ;
s->cmd_alloc_size_ = 0 ;
/* Initialize distance cache. */
s->dist_cache_[0 ] = 4 ;
s->dist_cache_[1 ] = 11 ;
s->dist_cache_[2 ] = 15 ;
s->dist_cache_[3 ] = 16 ;
/* Save the state of the distance cache in case we need to restore it for
emitting an uncompressed block. */
memcpy(s->saved_dist_cache_, s->dist_cache_, sizeof (s->saved_dist_cache_));
}
BrotliEncoderState* BrotliEncoderCreateInstance(
brotli_alloc_func alloc_func, brotli_free_func free_func, void * opaque) {
BROTLI_BOOL healthy = BrotliEncoderEnsureStaticInit();
if (!healthy) {
return 0 ;
}
BrotliEncoderState* state = (BrotliEncoderState*)BrotliBootstrapAlloc(
sizeof (BrotliEncoderState), alloc_func, free_func, opaque);
if (state == NULL) {
/* BROTLI_DUMP(); */
return 0 ;
}
BrotliInitMemoryManager(
&state->memory_manager_, alloc_func, free_func, opaque);
BrotliEncoderInitState(state);
return state;
}
static void BrotliEncoderCleanupState(BrotliEncoderState* s) {
MemoryManager* m = &s->memory_manager_;
BROTLI_ENCODER_ON_FINISH(s);
if (BROTLI_IS_OOM(m)) {
BrotliWipeOutMemoryManager(m);
return ;
}
BROTLI_FREE(m, s->storage_);
BROTLI_FREE(m, s->commands_);
RingBufferFree(m, &s->ringbuffer_);
DestroyHasher(m, &s->hasher_);
BROTLI_FREE(m, s->large_table_);
BROTLI_FREE(m, s->one_pass_arena_);
BROTLI_FREE(m, s->two_pass_arena_);
BROTLI_FREE(m, s->command_buf_);
BROTLI_FREE(m, s->literal_buf_);
BrotliEncoderCleanupParams(m, &s->params);
}
/* Deinitializes and frees BrotliEncoderState instance. */
void BrotliEncoderDestroyInstance(BrotliEncoderState* state) {
if (!state) {
return ;
} else {
BrotliEncoderCleanupState(state);
BrotliBootstrapFree(state, &state->memory_manager_);
}
}
/*
Copies the given input data to the internal ring buffer of the compressor .
No processing of the data occurs at this time and this function can be
called multiple times before calling WriteBrotliData ( ) to process the
accumulated input . At most input_block_size ( ) bytes of input data can be
copied to the ring buffer , otherwise the next WriteBrotliData ( ) will fail .
*/
static void CopyInputToRingBuffer(BrotliEncoderState* s,
const size_t input_size,
const uint8_t* input_buffer) {
RingBuffer* ringbuffer_ = &s->ringbuffer_;
MemoryManager* m = &s->memory_manager_;
RingBufferWrite(m, input_buffer, input_size, ringbuffer_);
if (BROTLI_IS_OOM(m)) return ;
s->input_pos_ += input_size;
/* TL;DR: If needed, initialize 7 more bytes in the ring buffer to make the
hashing not depend on uninitialized data . This makes compression
deterministic and it prevents uninitialized memory warnings in Valgrind .
Even without erasing , the output would be valid ( but nondeterministic ) .
Background information : The compressor stores short ( at most 8 bytes )
substrings of the input already read in a hash table , and detects
repetitions by looking up such substrings in the hash table . If it
can find a substring , it checks whether the substring is really there
in the ring buffer ( or it ' s just a hash collision ) . Should the hash
table become corrupt , this check makes sure that the output is
still valid , albeit the compression ratio would be bad .
The compressor populates the hash table from the ring buffer as it ' s
reading new bytes from the input . However , at the last few indexes of
the ring buffer , there are not enough bytes to build full - length
substrings from . Since the hash table always contains full - length
substrings , we overwrite with zeros here to make sure that those
substrings will contain zeros at the end instead of uninitialized
data .
Please note that erasing is not necessary ( because the
memory region is already initialized since he ring buffer
has a ` tail ' that holds a copy of the beginning , ) so we
skip erasing if we have already gone around at least once in
the ring buffer .
Only clear during the first round of ring - buffer writes . On
subsequent rounds data in the ring-buffer would be affected. */
if (ringbuffer_->pos_ <= ringbuffer_->mask_) {
/* This is the first time when the ring buffer is being written.
We clear 7 bytes just after the bytes that have been copied from
the input buffer .
The ring - buffer has a " tail " that holds a copy of the beginning ,
but only once the ring buffer has been fully written once , i . e . ,
pos < = mask . For the first time , we need to write values
in this tail ( where index may be larger than mask ) , so that
we have exactly defined behavior and don ' t read uninitialized
memory . Due to performance reasons , hashing reads data using a
LOAD64 , which can go 7 bytes beyond the bytes written in the
ring-buffer. */
memset(ringbuffer_->buffer_ + ringbuffer_->pos_, 0 , 7 );
}
}
/* Marks all input as processed.
Returns true if position wrapping occurs. */
static BROTLI_BOOL UpdateLastProcessedPos(BrotliEncoderState* s) {
uint32_t wrapped_last_processed_pos = WrapPosition(s->last_processed_pos_);
uint32_t wrapped_input_pos = WrapPosition(s->input_pos_);
s->last_processed_pos_ = s->input_pos_;
return TO_BROTLI_BOOL(wrapped_input_pos < wrapped_last_processed_pos);
}
static void ExtendLastCommand(BrotliEncoderState* s, uint32_t* bytes,
uint32_t* wrapped_last_processed_pos) {
Command* last_command = &s->commands_[s->num_commands_ - 1 ];
const uint8_t* data = s->ringbuffer_.buffer_;
const uint32_t mask = s->ringbuffer_.mask_;
uint64_t max_backward_distance =
(((uint64_t)1 ) << s->params.lgwin) - BROTLI_WINDOW_GAP;
uint64_t last_copy_len = last_command->copy_len_ & 0 x1FFFFFF;
uint64_t last_processed_pos = s->last_processed_pos_ - last_copy_len;
uint64_t max_distance = last_processed_pos < max_backward_distance ?
last_processed_pos : max_backward_distance;
uint64_t cmd_dist = (uint64_t)s->dist_cache_[0 ];
uint32_t distance_code = CommandRestoreDistanceCode(last_command,
&s->params.dist);
const CompoundDictionary* dict = &s->params.dictionary.compound;
size_t compound_dictionary_size = dict->total_size;
if (distance_code < BROTLI_NUM_DISTANCE_SHORT_CODES ||
distance_code - (BROTLI_NUM_DISTANCE_SHORT_CODES - 1 ) == cmd_dist) {
if (cmd_dist <= max_distance) {
while (*bytes != 0 && data[*wrapped_last_processed_pos & mask] ==
data[(*wrapped_last_processed_pos - cmd_dist) & mask]) {
last_command->copy_len_++;
(*bytes)--;
(*wrapped_last_processed_pos)++;
}
} else {
if ((cmd_dist - max_distance - 1 ) < compound_dictionary_size &&
last_copy_len < cmd_dist - max_distance) {
size_t address =
compound_dictionary_size - (size_t)(cmd_dist - max_distance) +
(size_t)last_copy_len;
size_t br_index = 0 ;
size_t br_offset;
const uint8_t* chunk;
size_t chunk_length;
while (address >= dict->chunk_offsets[br_index + 1 ]) br_index++;
br_offset = address - dict->chunk_offsets[br_index];
chunk = dict->chunk_source[br_index];
chunk_length =
dict->chunk_offsets[br_index + 1 ] - dict->chunk_offsets[br_index];
while (*bytes != 0 && data[*wrapped_last_processed_pos & mask] ==
chunk[br_offset]) {
last_command->copy_len_++;
(*bytes)--;
(*wrapped_last_processed_pos)++;
if (++br_offset == chunk_length) {
br_index++;
br_offset = 0 ;
if (br_index != dict->num_chunks) {
chunk = dict->chunk_source[br_index];
chunk_length = dict->chunk_offsets[br_index + 1 ] -
dict->chunk_offsets[br_index];
} else {
break ;
}
}
}
}
}
/* The copy length is at most the metablock size, and thus expressible. */
GetLengthCode(last_command->insert_len_,
(size_t)((int )(last_command->copy_len_ & 0 x1FFFFFF) +
(int )(last_command->copy_len_ >> 25 )),
TO_BROTLI_BOOL((last_command->dist_prefix_ & 0 x3FF) == 0 ),
&last_command->cmd_prefix_);
}
}
/*
Processes the accumulated input data and sets | * out_size | to the length of
the new output meta - block , or to zero if no new output meta - block has been
created ( in this case the processed input data is buffered internally ) .
If | * out_size | is positive , | * output | points to the start of the output
data . If | is_last | or | force_flush | is BROTLI_TRUE , an output meta - block is
always created . However , until | is_last | is BROTLI_TRUE encoder may retain up
to 7 bits of the last byte of output . Byte - alignment could be enforced by
emitting an empty meta - data block .
Returns BROTLI_FALSE if the size of the input data is larger than
input_block_size ( ) .
*/
static BROTLI_BOOL EncodeData(
BrotliEncoderState* s, const BROTLI_BOOL is_last,
const BROTLI_BOOL force_flush, size_t* out_size, uint8_t** output) {
const uint64_t delta = UnprocessedInputSize(s);
uint32_t bytes = (uint32_t)delta;
uint32_t wrapped_last_processed_pos = WrapPosition(s->last_processed_pos_);
uint8_t* data;
uint32_t mask;
MemoryManager* m = &s->memory_manager_;
ContextType literal_context_mode;
ContextLut literal_context_lut;
BROTLI_BOOL fast_compress =
s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY ||
s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY;
data = s->ringbuffer_.buffer_;
mask = s->ringbuffer_.mask_;
if (delta == 0 ) { /* No new input; still might want to flush or finish. */
if (!data) { /* No input has been processed so far. */
if (is_last) { /* Emit complete finalized stream. */
BROTLI_DCHECK(s->last_bytes_bits_ <= 14 );
s->last_bytes_ |= (uint16_t)(3 u << s->last_bytes_bits_);
s->last_bytes_bits_ = (uint8_t)(s->last_bytes_bits_ + 2 u);
s->tiny_buf_.u8[0 ] = (uint8_t)s->last_bytes_;
s->tiny_buf_.u8[1 ] = (uint8_t)(s->last_bytes_ >> 8 );
*output = s->tiny_buf_.u8;
*out_size = (s->last_bytes_bits_ + 7 u) >> 3 u;
return BROTLI_TRUE;
} else { /* No data, not last -> no-op. */
*out_size = 0 ;
return BROTLI_TRUE;
}
} else {
/* Fast compress performs flush every block -> flush is no-op. */
if (!is_last && (!force_flush || fast_compress)) { /* Another no-op. */
*out_size = 0 ;
return BROTLI_TRUE;
}
}
}
BROTLI_DCHECK(data);
if (s->params.quality > s->params.dictionary.max_quality) return BROTLI_FALSE;
/* Adding more blocks after "last" block is forbidden. */
if (s->is_last_block_emitted_) return BROTLI_FALSE;
if (is_last) s->is_last_block_emitted_ = BROTLI_TRUE;
if (delta > InputBlockSize(s)) {
return BROTLI_FALSE;
}
if (s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY &&
!s->command_buf_) {
s->command_buf_ =
BROTLI_ALLOC(m, uint32_t, kCompressFragmentTwoPassBlockSize);
s->literal_buf_ =
BROTLI_ALLOC(m, uint8_t, kCompressFragmentTwoPassBlockSize);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(s->command_buf_) ||
BROTLI_IS_NULL(s->literal_buf_)) {
return BROTLI_FALSE;
}
}
if (fast_compress) {
uint8_t* storage;
size_t storage_ix = s->last_bytes_bits_;
size_t table_size;
int * table;
storage = GetBrotliStorage(s, 2 * bytes + 503 );
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
storage[0 ] = (uint8_t)s->last_bytes_;
storage[1 ] = (uint8_t)(s->last_bytes_ >> 8 );
table = GetHashTable(s, s->params.quality, bytes, &table_size);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
if (s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY) {
BrotliCompressFragmentFast(
s->one_pass_arena_, &data[wrapped_last_processed_pos & mask],
bytes, is_last,
table, table_size,
&storage_ix, storage);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
} else {
BrotliCompressFragmentTwoPass(
s->two_pass_arena_, &data[wrapped_last_processed_pos & mask],
bytes, is_last,
s->command_buf_, s->literal_buf_,
table, table_size,
&storage_ix, storage);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
}
s->last_bytes_ = (uint16_t)(storage[storage_ix >> 3 ]);
s->last_bytes_bits_ = storage_ix & 7 u;
UpdateLastProcessedPos(s);
*output = &storage[0 ];
*out_size = storage_ix >> 3 ;
return BROTLI_TRUE;
}
{
/* Theoretical max number of commands is 1 per 2 bytes. */
size_t newsize = s->num_commands_ + bytes / 2 + 1 ;
if (newsize > s->cmd_alloc_size_) {
Command* new_commands;
/* Reserve a bit more memory to allow merging with a next block
without reallocation: that would impact speed. */
newsize += (bytes / 4 ) + 16 ;
s->cmd_alloc_size_ = newsize;
new_commands = BROTLI_ALLOC(m, Command, newsize);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(new_commands)) return BROTLI_FALSE;
if (s->commands_) {
memcpy(new_commands, s->commands_, sizeof (Command) * s->num_commands_);
BROTLI_FREE(m, s->commands_);
}
s->commands_ = new_commands;
}
}
InitOrStitchToPreviousBlock(m, &s->hasher_, data, mask, &s->params,
wrapped_last_processed_pos, bytes, is_last);
literal_context_mode = ChooseContextMode(
&s->params, data, WrapPosition(s->last_flush_pos_),
mask, (size_t)(s->input_pos_ - s->last_flush_pos_));
literal_context_lut = BROTLI_CONTEXT_LUT(literal_context_mode);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
if (s->num_commands_ && s->last_insert_len_ == 0 ) {
ExtendLastCommand(s, &bytes, &wrapped_last_processed_pos);
}
if (s->params.quality == ZOPFLIFICATION_QUALITY) {
BROTLI_DCHECK(s->params.hasher.type == 10 );
BrotliCreateZopfliBackwardReferences(m, bytes, wrapped_last_processed_pos,
data, mask, literal_context_lut, &s->params,
&s->hasher_, s->dist_cache_,
&s->last_insert_len_, &s->commands_[s->num_commands_],
&s->num_commands_, &s->num_literals_);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
} else if (s->params.quality == HQ_ZOPFLIFICATION_QUALITY) {
BROTLI_DCHECK(s->params.hasher.type == 10 );
BrotliCreateHqZopfliBackwardReferences(m, bytes, wrapped_last_processed_pos,
data, mask, literal_context_lut, &s->params,
&s->hasher_, s->dist_cache_,
&s->last_insert_len_, &s->commands_[s->num_commands_],
&s->num_commands_, &s->num_literals_);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
} else {
BrotliCreateBackwardReferences(bytes, wrapped_last_processed_pos,
data, mask, literal_context_lut, &s->params,
&s->hasher_, s->dist_cache_,
&s->last_insert_len_, &s->commands_[s->num_commands_],
&s->num_commands_, &s->num_literals_);
}
{
const size_t max_length = MaxMetablockSize(&s->params);
const size_t max_literals = max_length / 8 ;
const size_t max_commands = max_length / 8 ;
const size_t processed_bytes = (size_t)(s->input_pos_ - s->last_flush_pos_);
/* If maximal possible additional block doesn't fit metablock, flush now. */
/* TODO(eustas): Postpone decision until next block arrives? */
const BROTLI_BOOL next_input_fits_metablock = TO_BROTLI_BOOL(
processed_bytes + InputBlockSize(s) <= max_length);
/* If block splitting is not used, then flush as soon as there is some
amount of commands / literals produced. */
const BROTLI_BOOL should_flush = TO_BROTLI_BOOL(
s->params.quality < MIN_QUALITY_FOR_BLOCK_SPLIT &&
s->num_literals_ + s->num_commands_ >= MAX_NUM_DELAYED_SYMBOLS);
if (!is_last && !force_flush && !should_flush &&
next_input_fits_metablock &&
s->num_literals_ < max_literals &&
s->num_commands_ < max_commands) {
/* Merge with next input block. Everything will happen later. */
if (UpdateLastProcessedPos(s)) {
HasherReset(&s->hasher_);
}
*out_size = 0 ;
return BROTLI_TRUE;
}
}
/* Create the last insert-only command. */
if (s->last_insert_len_ > 0 ) {
InitInsertCommand(&s->commands_[s->num_commands_++], s->last_insert_len_);
s->num_literals_ += s->last_insert_len_;
s->last_insert_len_ = 0 ;
}
if (!is_last && s->input_pos_ == s->last_flush_pos_) {
/* We have no new input data and we don't have to finish the stream, so
nothing to do. */
*out_size = 0 ;
return BROTLI_TRUE;
}
BROTLI_DCHECK(s->input_pos_ >= s->last_flush_pos_);
BROTLI_DCHECK(s->input_pos_ > s->last_flush_pos_ || is_last);
BROTLI_DCHECK(s->input_pos_ - s->last_flush_pos_ <= 1 u << 24 );
{
const uint32_t metablock_size =
(uint32_t)(s->input_pos_ - s->last_flush_pos_);
uint8_t* storage = GetBrotliStorage(s, 2 * metablock_size + 503 );
size_t storage_ix = s->last_bytes_bits_;
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
storage[0 ] = (uint8_t)s->last_bytes_;
storage[1 ] = (uint8_t)(s->last_bytes_ >> 8 );
WriteMetaBlockInternal(
m, data, mask, s->last_flush_pos_, metablock_size, is_last,
literal_context_mode, &s->params, s->prev_byte_, s->prev_byte2_,
s->num_literals_, s->num_commands_, s->commands_, s->saved_dist_cache_,
s->dist_cache_, &storage_ix, storage);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
s->last_bytes_ = (uint16_t)(storage[storage_ix >> 3 ]);
s->last_bytes_bits_ = storage_ix & 7 u;
s->last_flush_pos_ = s->input_pos_;
if (UpdateLastProcessedPos(s)) {
HasherReset(&s->hasher_);
}
if (s->last_flush_pos_ > 0 ) {
s->prev_byte_ = data[((uint32_t)s->last_flush_pos_ - 1 ) & mask];
}
if (s->last_flush_pos_ > 1 ) {
s->prev_byte2_ = data[(uint32_t)(s->last_flush_pos_ - 2 ) & mask];
}
s->num_commands_ = 0 ;
s->num_literals_ = 0 ;
/* Save the state of the distance cache in case we need to restore it for
emitting an uncompressed block. */
memcpy(s->saved_dist_cache_, s->dist_cache_, sizeof (s->saved_dist_cache_));
*output = &storage[0 ];
*out_size = storage_ix >> 3 ;
return BROTLI_TRUE;
}
}
/* Dumps remaining output bits and metadata header to |header|.
Returns number of produced bytes .
REQUIRED : | header | should be 8 - byte aligned and at least 16 bytes long .
REQUIRED: |block_size| <= (1 << 24). */
static size_t WriteMetadataHeader(
BrotliEncoderState* s, const size_t block_size, uint8_t* header) {
size_t storage_ix;
storage_ix = s->last_bytes_bits_;
header[0 ] = (uint8_t)s->last_bytes_;
header[1 ] = (uint8_t)(s->last_bytes_ >> 8 );
s->last_bytes_ = 0 ;
s->last_bytes_bits_ = 0 ;
BrotliWriteBits(1 , 0 , &storage_ix, header);
BrotliWriteBits(2 , 3 , &storage_ix, header);
BrotliWriteBits(1 , 0 , &storage_ix, header);
if (block_size == 0 ) {
BrotliWriteBits(2 , 0 , &storage_ix, header);
} else {
uint32_t nbits = (block_size == 1 ) ? 1 :
(Log2FloorNonZero((uint32_t)block_size - 1 ) + 1 );
uint32_t nbytes = (nbits + 7 ) / 8 ;
BrotliWriteBits(2 , nbytes, &storage_ix, header);
BrotliWriteBits(8 * nbytes, block_size - 1 , &storage_ix, header);
}
return (storage_ix + 7 u) >> 3 ;
}
size_t BrotliEncoderMaxCompressedSize(size_t input_size) {
/* [window bits / empty metadata] + N * [uncompressed] + [last empty] */
size_t num_large_blocks = input_size >> 14 ;
size_t overhead = 2 + (4 * num_large_blocks) + 3 + 1 ;
size_t result = input_size + overhead;
if (input_size == 0 ) return 2 ;
return (result < input_size) ? 0 : result;
}
/* Wraps data to uncompressed brotli stream with minimal window size.
| output | should point at region with at least BrotliEncoderMaxCompressedSize
addressable bytes .
Returns the length of stream. */
static size_t MakeUncompressedStream(
const uint8_t* input, size_t input_size, uint8_t* output) {
size_t size = input_size;
size_t result = 0 ;
size_t offset = 0 ;
if (input_size == 0 ) {
output[0 ] = 6 ;
return 1 ;
}
output[result++] = 0 x21; /* window bits = 10, is_last = false */
output[result++] = 0 x03; /* empty metadata, padding */
while (size > 0 ) {
uint32_t nibbles = 0 ;
uint32_t chunk_size;
uint32_t bits;
chunk_size = (size > (1 u << 24 )) ? (1 u << 24 ) : (uint32_t)size;
if (chunk_size > (1 u << 16 )) nibbles = (chunk_size > (1 u << 20 )) ? 2 : 1 ;
bits =
(nibbles << 1 ) | ((chunk_size - 1 ) << 3 ) | (1 u << (19 + 4 * nibbles));
output[result++] = (uint8_t)bits;
output[result++] = (uint8_t)(bits >> 8 );
output[result++] = (uint8_t)(bits >> 16 );
if (nibbles == 2 ) output[result++] = (uint8_t)(bits >> 24 );
memcpy(&output[result], &input[offset], chunk_size);
result += chunk_size;
offset += chunk_size;
size -= chunk_size;
}
output[result++] = 3 ;
return result;
}
BROTLI_BOOL BrotliEncoderCompress(
int quality, int lgwin, BrotliEncoderMode mode, size_t input_size,
const uint8_t input_buffer[BROTLI_ARRAY_PARAM(input_size)],
size_t* encoded_size,
uint8_t encoded_buffer[BROTLI_ARRAY_PARAM(*encoded_size)]) {
BrotliEncoderState* s;
size_t out_size = *encoded_size;
const uint8_t* input_start = input_buffer;
uint8_t* output_start = encoded_buffer;
size_t max_out_size = BrotliEncoderMaxCompressedSize(input_size);
if (out_size == 0 ) {
/* Output buffer needs at least one byte. */
return BROTLI_FALSE;
}
if (input_size == 0 ) {
/* Handle the special case of empty input. */
*encoded_size = 1 ;
*encoded_buffer = 6 ;
return BROTLI_TRUE;
}
s = BrotliEncoderCreateInstance(0 , 0 , 0 );
if (!s) {
return BROTLI_FALSE;
} else {
size_t available_in = input_size;
const uint8_t* next_in = input_buffer;
size_t available_out = *encoded_size;
uint8_t* next_out = encoded_buffer;
size_t total_out = 0 ;
BROTLI_BOOL result = BROTLI_FALSE;
/* TODO(eustas): check that parameters are sane. */
BrotliEncoderSetParameter(s, BROTLI_PARAM_QUALITY, (uint32_t)quality);
BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)lgwin);
BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, (uint32_t)mode);
BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, (uint32_t)input_size);
if (lgwin > BROTLI_MAX_WINDOW_BITS) {
BrotliEncoderSetParameter(s, BROTLI_PARAM_LARGE_WINDOW, BROTLI_TRUE);
}
result = BrotliEncoderCompressStream(s, BROTLI_OPERATION_FINISH,
&available_in, &next_in, &available_out, &next_out, &total_out);
if (!BrotliEncoderIsFinished(s)) result = 0 ;
*encoded_size = total_out;
BrotliEncoderDestroyInstance(s);
if (!result || (max_out_size && *encoded_size > max_out_size)) {
goto fallback;
}
return BROTLI_TRUE;
}
fallback:
*encoded_size = 0 ;
if (!max_out_size) return BROTLI_FALSE;
if (out_size >= max_out_size) {
*encoded_size =
MakeUncompressedStream(input_start, input_size, output_start);
return BROTLI_TRUE;
}
return BROTLI_FALSE;
}
static void InjectBytePaddingBlock(BrotliEncoderState* s) {
uint32_t seal = s->last_bytes_;
size_t seal_bits = s->last_bytes_bits_;
uint8_t* destination;
s->last_bytes_ = 0 ;
s->last_bytes_bits_ = 0 ;
/* is_last = 0, data_nibbles = 11, reserved = 0, meta_nibbles = 00 */
seal |= 0 x6u << seal_bits;
seal_bits += 6 ;
/* If we have already created storage, then append to it.
Storage is valid until next block is being compressed. */
if (s->next_out_) {
destination = s->next_out_ + s->available_out_;
} else {
destination = s->tiny_buf_.u8;
s->next_out_ = destination;
}
destination[0 ] = (uint8_t)seal;
if (seal_bits > 8 ) destination[1 ] = (uint8_t)(seal >> 8 );
if (seal_bits > 16 ) destination[2 ] = (uint8_t)(seal >> 16 );
s->available_out_ += (seal_bits + 7 ) >> 3 ;
}
/* Fills the |total_out|, if it is not NULL. */
static void SetTotalOut(BrotliEncoderState* s, size_t* total_out) {
if (total_out) {
/* Saturating conversion uint64_t -> size_t */
size_t result = (size_t)-1 ;
if (s->total_out_ < result) {
result = (size_t)s->total_out_;
}
*total_out = result;
}
}
/* Injects padding bits or pushes compressed data to output.
Returns false if nothing is done. */
static BROTLI_BOOL InjectFlushOrPushOutput(BrotliEncoderState* s,
size_t* available_out, uint8_t** next_out, size_t* total_out) {
if (s->stream_state_ == BROTLI_STREAM_FLUSH_REQUESTED &&
s->last_bytes_bits_ != 0 ) {
InjectBytePaddingBlock(s);
return BROTLI_TRUE;
}
if (s->available_out_ != 0 && *available_out != 0 ) {
size_t copy_output_size =
BROTLI_MIN(size_t, s->available_out_, *available_out);
memcpy(*next_out, s->next_out_, copy_output_size);
*next_out += copy_output_size;
*available_out -= copy_output_size;
s->next_out_ += copy_output_size;
s->available_out_ -= copy_output_size;
s->total_out_ += copy_output_size;
SetTotalOut(s, total_out);
return BROTLI_TRUE;
}
return BROTLI_FALSE;
}
static void CheckFlushComplete(BrotliEncoderState* s) {
if (s->stream_state_ == BROTLI_STREAM_FLUSH_REQUESTED &&
s->available_out_ == 0 ) {
s->stream_state_ = BROTLI_STREAM_PROCESSING;
s->next_out_ = 0 ;
}
}
static BROTLI_BOOL BrotliEncoderCompressStreamFast(
BrotliEncoderState* s, BrotliEncoderOperation op, size_t* available_in,
const uint8_t** next_in, size_t* available_out, uint8_t** next_out,
size_t* total_out) {
const size_t block_size_limit = (size_t)1 << s->params.lgwin;
const size_t buf_size = BROTLI_MIN(size_t, kCompressFragmentTwoPassBlockSize,
BROTLI_MIN(size_t, *available_in, block_size_limit));
uint32_t* tmp_command_buf = NULL;
uint32_t* command_buf = NULL;
uint8_t* tmp_literal_buf = NULL;
uint8_t* literal_buf = NULL;
MemoryManager* m = &s->memory_manager_;
if (s->params.quality != FAST_ONE_PASS_COMPRESSION_QUALITY &&
s->params.quality != FAST_TWO_PASS_COMPRESSION_QUALITY) {
return BROTLI_FALSE;
}
if (s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY) {
if (!s->command_buf_ && buf_size == kCompressFragmentTwoPassBlockSize) {
s->command_buf_ =
BROTLI_ALLOC(m, uint32_t, kCompressFragmentTwoPassBlockSize);
s->literal_buf_ =
BROTLI_ALLOC(m, uint8_t, kCompressFragmentTwoPassBlockSize);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(s->command_buf_) ||
BROTLI_IS_NULL(s->literal_buf_)) {
return BROTLI_FALSE;
}
}
if (s->command_buf_) {
command_buf = s->command_buf_;
literal_buf = s->literal_buf_;
} else {
tmp_command_buf = BROTLI_ALLOC(m, uint32_t, buf_size);
tmp_literal_buf = BROTLI_ALLOC(m, uint8_t, buf_size);
if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(tmp_command_buf) ||
BROTLI_IS_NULL(tmp_literal_buf)) {
return BROTLI_FALSE;
}
command_buf = tmp_command_buf;
literal_buf = tmp_literal_buf;
}
}
while (BROTLI_TRUE) {
if (InjectFlushOrPushOutput(s, available_out, next_out, total_out)) {
continue ;
}
/* Compress block only when internal output buffer is empty, stream is not
finished , there is no pending flush request , and there is either
additional input or pending operation. */
if (s->available_out_ == 0 &&
s->stream_state_ == BROTLI_STREAM_PROCESSING &&
(*available_in != 0 || op != BROTLI_OPERATION_PROCESS)) {
size_t block_size = BROTLI_MIN(size_t, block_size_limit, *available_in);
BROTLI_BOOL is_last =
(*available_in == block_size) && (op == BROTLI_OPERATION_FINISH);
BROTLI_BOOL force_flush =
(*available_in == block_size) && (op == BROTLI_OPERATION_FLUSH);
size_t max_out_size = 2 * block_size + 503 ;
BROTLI_BOOL inplace = BROTLI_TRUE;
uint8_t* storage = NULL;
size_t storage_ix = s->last_bytes_bits_;
size_t table_size;
int * table;
if (force_flush && block_size == 0 ) {
s->stream_state_ = BROTLI_STREAM_FLUSH_REQUESTED;
continue ;
}
if (max_out_size <= *available_out) {
storage = *next_out;
} else {
inplace = BROTLI_FALSE;
storage = GetBrotliStorage(s, max_out_size);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
}
storage[0 ] = (uint8_t)s->last_bytes_;
storage[1 ] = (uint8_t)(s->last_bytes_ >> 8 );
table = GetHashTable(s, s->params.quality, block_size, &table_size);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
if (s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY) {
BrotliCompressFragmentFast(s->one_pass_arena_, *next_in, block_size,
is_last, table, table_size, &storage_ix, storage);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
} else {
BrotliCompressFragmentTwoPass(s->two_pass_arena_, *next_in, block_size,
is_last, command_buf, literal_buf, table, table_size,
&storage_ix, storage);
if (BROTLI_IS_OOM(m)) return BROTLI_FALSE;
}
if (block_size != 0 ) {
*next_in += block_size;
*available_in -= block_size;
s->total_in_ += block_size;
}
if (inplace) {
size_t out_bytes = storage_ix >> 3 ;
BROTLI_DCHECK(out_bytes <= *available_out);
BROTLI_DCHECK((storage_ix & 7 ) == 0 || out_bytes < *available_out);
*next_out += out_bytes;
*available_out -= out_bytes;
s->total_out_ += out_bytes;
SetTotalOut(s, total_out);
} else {
size_t out_bytes = storage_ix >> 3 ;
s->next_out_ = storage;
s->available_out_ = out_bytes;
}
s->last_bytes_ = (uint16_t)(storage[storage_ix >> 3 ]);
s->last_bytes_bits_ = storage_ix & 7 u;
if (force_flush) s->stream_state_ = BROTLI_STREAM_FLUSH_REQUESTED;
if (is_last) s->stream_state_ = BROTLI_STREAM_FINISHED;
continue ;
}
break ;
}
BROTLI_FREE(m, tmp_command_buf);
BROTLI_FREE(m, tmp_literal_buf);
CheckFlushComplete(s);
return BROTLI_TRUE;
}
static BROTLI_BOOL ProcessMetadata(
BrotliEncoderState* s, size_t* available_in, const uint8_t** next_in,
size_t* available_out, uint8_t** next_out, size_t* total_out) {
if (*available_in > (1 u << 24 )) return BROTLI_FALSE;
/* Switch to metadata block workflow, if required. */
if (s->stream_state_ == BROTLI_STREAM_PROCESSING) {
s->remaining_metadata_bytes_ = (uint32_t)*available_in;
s->stream_state_ = BROTLI_STREAM_METADATA_HEAD;
}
if (s->stream_state_ != BROTLI_STREAM_METADATA_HEAD &&
s->stream_state_ != BROTLI_STREAM_METADATA_BODY) {
return BROTLI_FALSE;
}
while (BROTLI_TRUE) {
if (InjectFlushOrPushOutput(s, available_out, next_out, total_out)) {
continue ;
}
if (s->available_out_ != 0 ) break ;
if (s->input_pos_ != s->last_flush_pos_) {
BROTLI_BOOL result = EncodeData(s, BROTLI_FALSE, BROTLI_TRUE,
&s->available_out_, &s->next_out_);
if (!result) return BROTLI_FALSE;
continue ;
}
if (s->stream_state_ == BROTLI_STREAM_METADATA_HEAD) {
s->next_out_ = s->tiny_buf_.u8;
s->available_out_ =
WriteMetadataHeader(s, s->remaining_metadata_bytes_, s->next_out_);
s->stream_state_ = BROTLI_STREAM_METADATA_BODY;
continue ;
} else {
/* Exit workflow only when there is no more input and no more output.
Otherwise client may continue producing empty metadata blocks. */
if (s->remaining_metadata_bytes_ == 0 ) {
s->remaining_metadata_bytes_ = BROTLI_UINT32_MAX;
s->stream_state_ = BROTLI_STREAM_PROCESSING;
break ;
}
if (*available_out) {
/* Directly copy input to output. */
uint32_t copy = (uint32_t)BROTLI_MIN(
size_t, s->remaining_metadata_bytes_, *available_out);
memcpy(*next_out, *next_in, copy);
*next_in += copy;
*available_in -= copy;
s->total_in_ += copy; /* not actually data input, though */
s->remaining_metadata_bytes_ -= copy;
*next_out += copy;
*available_out -= copy;
} else {
/* This guarantees progress in "TakeOutput" workflow. */
uint32_t copy = BROTLI_MIN(uint32_t, s->remaining_metadata_bytes_, 16 );
s->next_out_ = s->tiny_buf_.u8;
memcpy(s->next_out_, *next_in, copy);
*next_in += copy;
*available_in -= copy;
s->total_in_ += copy; /* not actually data input, though */
s->remaining_metadata_bytes_ -= copy;
s->available_out_ = copy;
}
continue ;
}
}
return BROTLI_TRUE;
}
static void UpdateSizeHint(BrotliEncoderState* s, size_t available_in) {
if (s->params.size_hint == 0 ) {
uint64_t delta = UnprocessedInputSize(s);
uint64_t tail = available_in;
uint32_t limit = 1 u << 30 ;
uint32_t total;
if ((delta >= limit) || (tail >= limit) || ((delta + tail) >= limit)) {
total = limit;
} else {
total = (uint32_t)(delta + tail);
}
s->params.size_hint = total;
}
}
BROTLI_BOOL BrotliEncoderCompressStream(
BrotliEncoderState* s, BrotliEncoderOperation op, size_t* available_in,
const uint8_t** next_in, size_t* available_out, uint8_t** next_out,
size_t* total_out) {
if (!EnsureInitialized(s)) return BROTLI_FALSE;
/* Unfinished metadata block; check requirements. */
if (s->remaining_metadata_bytes_ != BROTLI_UINT32_MAX) {
if (*available_in != s->remaining_metadata_bytes_) return BROTLI_FALSE;
if (op != BROTLI_OPERATION_EMIT_METADATA) return BROTLI_FALSE;
}
if (op == BROTLI_OPERATION_EMIT_METADATA) {
UpdateSizeHint(s, 0 ); /* First data metablock might be emitted here. */
return ProcessMetadata(
s, available_in, next_in, available_out, next_out, total_out);
}
if (s->stream_state_ == BROTLI_STREAM_METADATA_HEAD ||
s->stream_state_ == BROTLI_STREAM_METADATA_BODY) {
return BROTLI_FALSE;
}
if (s->stream_state_ != BROTLI_STREAM_PROCESSING && *available_in != 0 ) {
return BROTLI_FALSE;
}
if (s->params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY ||
s->params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY) {
return BrotliEncoderCompressStreamFast(s, op, available_in, next_in,
available_out, next_out, total_out);
}
while (BROTLI_TRUE) {
size_t remaining_block_size = RemainingInputBlockSize(s);
/* Shorten input to flint size. */
if (s->flint_ >= 0 && remaining_block_size > (size_t)s->flint_) {
remaining_block_size = (size_t)s->flint_;
}
if (remaining_block_size != 0 && *available_in != 0 ) {
size_t copy_input_size =
BROTLI_MIN(size_t, remaining_block_size, *available_in);
CopyInputToRingBuffer(s, copy_input_size, *next_in);
*next_in += copy_input_size;
*available_in -= copy_input_size;
s->total_in_ += copy_input_size;
if (s->flint_ > 0 ) s->flint_ = (int8_t)(s->flint_ - (int )copy_input_size);
continue ;
}
if (InjectFlushOrPushOutput(s, available_out, next_out, total_out)) {
/* Exit the "emit flint" workflow. */
if (s->flint_ == BROTLI_FLINT_WAITING_FOR_FLUSHING) {
CheckFlushComplete(s);
if (s->stream_state_ == BROTLI_STREAM_PROCESSING) {
s->flint_ = BROTLI_FLINT_DONE;
}
}
continue ;
}
/* Compress data only when internal output buffer is empty, stream is not
finished and there is no pending flush request. */
if (s->available_out_ == 0 &&
s->stream_state_ == BROTLI_STREAM_PROCESSING) {
if (remaining_block_size == 0 || op != BROTLI_OPERATION_PROCESS) {
BROTLI_BOOL is_last = TO_BROTLI_BOOL(
(*available_in == 0 ) && op == BROTLI_OPERATION_FINISH);
BROTLI_BOOL force_flush = TO_BROTLI_BOOL(
(*available_in == 0 ) && op == BROTLI_OPERATION_FLUSH);
BROTLI_BOOL result;
/* Force emitting (uncompressed) piece containing flint. */
if (!is_last && s->flint_ == 0 ) {
s->flint_ = BROTLI_FLINT_WAITING_FOR_FLUSHING;
force_flush = BROTLI_TRUE;
}
UpdateSizeHint(s, *available_in);
result = EncodeData(s, is_last, force_flush,
&s->available_out_, &s->next_out_);
if (!result) return BROTLI_FALSE;
if (force_flush) s->stream_state_ = BROTLI_STREAM_FLUSH_REQUESTED;
if (is_last) s->stream_state_ = BROTLI_STREAM_FINISHED;
continue ;
}
}
break ;
}
CheckFlushComplete(s);
return BROTLI_TRUE;
}
BROTLI_BOOL BrotliEncoderIsFinished(BrotliEncoderState* s) {
return TO_BROTLI_BOOL(s->stream_state_ == BROTLI_STREAM_FINISHED &&
!BrotliEncoderHasMoreOutput(s));
}
BROTLI_BOOL BrotliEncoderHasMoreOutput(BrotliEncoderState* s) {
return TO_BROTLI_BOOL(s->available_out_ != 0 );
}
const uint8_t* BrotliEncoderTakeOutput(BrotliEncoderState* s, size_t* size) {
size_t consumed_size = s->available_out_;
uint8_t* result = s->next_out_;
if (*size) {
consumed_size = BROTLI_MIN(size_t, *size, s->available_out_);
}
if (consumed_size) {
s->next_out_ += consumed_size;
s->available_out_ -= consumed_size;
s->total_out_ += consumed_size;
CheckFlushComplete(s);
*size = consumed_size;
} else {
*size = 0 ;
result = 0 ;
}
return result;
}
uint32_t BrotliEncoderVersion(void ) {
return BROTLI_VERSION;
}
BrotliEncoderPreparedDictionary* BrotliEncoderPrepareDictionary(
BrotliSharedDictionaryType type, size_t size,
const uint8_t data[BROTLI_ARRAY_PARAM(size)], int quality,
brotli_alloc_func alloc_func, brotli_free_func free_func, void * opaque) {
ManagedDictionary* managed_dictionary = NULL;
BROTLI_BOOL type_is_known = BROTLI_FALSE;
type_is_known |= (type == BROTLI_SHARED_DICTIONARY_RAW);
#if defined (BROTLI_EXPERIMENTAL)
type_is_known |= (type == BROTLI_SHARED_DICTIONARY_SERIALIZED);
#endif /* BROTLI_EXPERIMENTAL */
if (!type_is_known) {
return NULL;
}
managed_dictionary =
BrotliCreateManagedDictionary(alloc_func, free_func, opaque);
if (managed_dictionary == NULL) {
return NULL;
}
if (type == BROTLI_SHARED_DICTIONARY_RAW) {
managed_dictionary->dictionary = (uint32_t*)CreatePreparedDictionary(
&managed_dictionary->memory_manager_, data, size);
}
#if defined (BROTLI_EXPERIMENTAL)
if (type == BROTLI_SHARED_DICTIONARY_SERIALIZED) {
SharedEncoderDictionary* dict = (SharedEncoderDictionary*)BrotliAllocate(
&managed_dictionary->memory_manager_, sizeof (SharedEncoderDictionary));
managed_dictionary->dictionary = (uint32_t*)dict;
if (dict != NULL) {
BROTLI_BOOL ok = BrotliInitCustomSharedEncoderDictionary(
&managed_dictionary->memory_manager_, data, size, quality, dict);
if (!ok) {
BrotliFree(&managed_dictionary->memory_manager_, dict);
managed_dictionary->dictionary = NULL;
}
}
}
#else /* BROTLI_EXPERIMENTAL */
(void )quality;
#endif /* BROTLI_EXPERIMENTAL */
if (managed_dictionary->dictionary == NULL) {
BrotliDestroyManagedDictionary(managed_dictionary);
return NULL;
}
return (BrotliEncoderPreparedDictionary*)managed_dictionary;
}
void BROTLI_COLD BrotliEncoderDestroyPreparedDictionary(
BrotliEncoderPreparedDictionary* dictionary) {
ManagedDictionary* dict = (ManagedDictionary*)dictionary;
if (!dictionary) return ;
/* First field of dictionary structs. */
/* Only managed dictionaries are eligible for destruction by this method. */
if (dict->magic != kManagedDictionaryMagic) {
return ;
}
if (dict->dictionary == NULL) {
/* This should never ever happen. */
} else if (*dict->dictionary == kLeanPreparedDictionaryMagic) {
DestroyPreparedDictionary(
&dict->memory_manager_, (PreparedDictionary*)dict->dictionary);
} else if (*dict->dictionary == kSharedDictionaryMagic) {
BrotliCleanupSharedEncoderDictionary(&dict->memory_manager_,
(SharedEncoderDictionary*)dict->dictionary);
BrotliFree(&dict->memory_manager_, dict->dictionary);
} else {
/* There is also kPreparedDictionaryMagic, but such instances should be
* constructed and destroyed by different means. */
}
dict->dictionary = NULL;
BrotliDestroyManagedDictionary(dict);
}
BROTLI_BOOL BROTLI_COLD BrotliEncoderAttachPreparedDictionary(
BrotliEncoderState* state,
const BrotliEncoderPreparedDictionary* dictionary) {
/* First field of dictionary structs */
const BrotliEncoderPreparedDictionary* dict = dictionary;
uint32_t magic = *((const uint32_t*)dict);
SharedEncoderDictionary* current = NULL;
if (magic == kManagedDictionaryMagic) {
/* Unwrap managed dictionary. */
ManagedDictionary* managed_dictionary = (ManagedDictionary*)dict;
magic = *managed_dictionary->dictionary;
dict = (BrotliEncoderPreparedDictionary*)managed_dictionary->dictionary;
}
current = &state->params.dictionary;
if (magic == kPreparedDictionaryMagic ||
magic == kLeanPreparedDictionaryMagic) {
const PreparedDictionary* prepared = (const PreparedDictionary*)dict;
if (!AttachPreparedDictionary(¤t->compound, prepared)) {
return BROTLI_FALSE;
}
} else if (magic == kSharedDictionaryMagic) {
const SharedEncoderDictionary* attached =
(const SharedEncoderDictionary*)dict;
BROTLI_BOOL was_default = !current->contextual.context_based &&
current->contextual.num_dictionaries == 1 &&
current->contextual.dict[0 ]->hash_table_words ==
kStaticDictionaryHashWords &&
current->contextual.dict[0 ]->hash_table_lengths ==
kStaticDictionaryHashLengths;
BROTLI_BOOL new_default = !attached->contextual.context_based &&
attached->contextual.num_dictionaries == 1 &&
attached->contextual.dict[0 ]->hash_table_words ==
kStaticDictionaryHashWords &&
attached->contextual.dict[0 ]->hash_table_lengths ==
kStaticDictionaryHashLengths;
size_t i;
if (state->is_initialized_) return BROTLI_FALSE;
current->max_quality =
BROTLI_MIN(int , current->max_quality, attached->max_quality);
for (i = 0 ; i < attached->compound.num_chunks; i++) {
if (!AttachPreparedDictionary(¤t->compound,
attached->compound.chunks[i])) {
return BROTLI_FALSE;
}
}
if (!new_default) {
if (!was_default) return BROTLI_FALSE;
/* Copy by value, but then set num_instances_ to 0 because their memory
is managed by attached, not by current */
current->contextual = attached->contextual;
current->contextual.num_instances_ = 0 ;
}
} else {
return BROTLI_FALSE;
}
return BROTLI_TRUE;
}
size_t BROTLI_COLD BrotliEncoderEstimatePeakMemoryUsage(int quality, int lgwin,
size_t input_size) {
BrotliEncoderParams params;
size_t memory_manager_slots = BROTLI_ENCODER_MEMORY_MANAGER_SLOTS;
size_t memory_manager_size = memory_manager_slots * sizeof (void *);
BrotliEncoderInitParams(¶ms);
params.quality = quality;
params.lgwin = lgwin;
params.size_hint = input_size;
params.large_window = lgwin > BROTLI_MAX_WINDOW_BITS;
SanitizeParams(¶ms);
params.lgblock = ComputeLgBlock(¶ms);
ChooseHasher(¶ms, ¶ms.hasher);
if (params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY ||
params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY) {
size_t state_size = sizeof (BrotliEncoderState);
size_t block_size = BROTLI_MIN(size_t, input_size, ((size_t)1 ul << params.lgwin));
size_t hash_table_size =
HashTableSize(MaxHashTableSize(params.quality), block_size);
size_t hash_size =
(hash_table_size < (1 u << 10 )) ? 0 : sizeof (int ) * hash_table_size;
size_t cmdbuf_size = params.quality == FAST_TWO_PASS_COMPRESSION_QUALITY ?
5 * BROTLI_MIN(size_t, block_size, 1 ul << 17 ) : 0 ;
if (params.quality == FAST_ONE_PASS_COMPRESSION_QUALITY) {
state_size += sizeof (BrotliOnePassArena);
} else {
state_size += sizeof (BrotliTwoPassArena);
}
return hash_size + cmdbuf_size + state_size;
} else {
size_t short_ringbuffer_size = (size_t)1 << params.lgblock;
int ringbuffer_bits = ComputeRbBits(¶ms);
size_t ringbuffer_size = input_size < short_ringbuffer_size ?
input_size : ((size_t)1 u << ringbuffer_bits) + short_ringbuffer_size;
size_t hash_size[4 ] = {0 };
size_t metablock_size =
BROTLI_MIN(size_t, input_size, MaxMetablockSize(¶ms));
size_t inputblock_size =
BROTLI_MIN(size_t, input_size, (size_t)1 << params.lgblock);
size_t cmdbuf_size = metablock_size * 2 + inputblock_size * 6 ;
size_t outbuf_size = metablock_size * 2 + 503 ;
size_t histogram_size = 0 ;
HasherSize(¶ms, BROTLI_TRUE, input_size, hash_size);
if (params.quality < MIN_QUALITY_FOR_BLOCK_SPLIT) {
cmdbuf_size = BROTLI_MIN(size_t, cmdbuf_size,
MAX_NUM_DELAYED_SYMBOLS * sizeof (Command) + inputblock_size * 12 );
}
if (params.quality >= MIN_QUALITY_FOR_HQ_BLOCK_SPLITTING) {
/* Only a very rough estimation, based on enwik8. */
histogram_size = 200 << 20 ;
} else if (params.quality >= MIN_QUALITY_FOR_BLOCK_SPLIT) {
size_t literal_histograms =
BROTLI_MIN(size_t, metablock_size / 6144 , 256 );
size_t command_histograms =
BROTLI_MIN(size_t, metablock_size / 6144 , 256 );
size_t distance_histograms =
BROTLI_MIN(size_t, metablock_size / 6144 , 256 );
histogram_size = literal_histograms * sizeof (HistogramLiteral) +
command_histograms * sizeof (HistogramCommand) +
distance_histograms * sizeof (HistogramDistance);
}
return (memory_manager_size + ringbuffer_size +
hash_size[0 ] + hash_size[1 ] + hash_size[2 ] + hash_size[3 ] +
cmdbuf_size +
outbuf_size +
histogram_size);
}
}
size_t BROTLI_COLD BrotliEncoderGetPreparedDictionarySize(
const BrotliEncoderPreparedDictionary* prepared_dictionary) {
/* First field of dictionary structs */
const BrotliEncoderPreparedDictionary* prepared = prepared_dictionary;
uint32_t magic = *((const uint32_t*)prepared);
size_t overhead = 0 ;
if (magic == kManagedDictionaryMagic) {
const ManagedDictionary* managed = (const ManagedDictionary*)prepared;
overhead = sizeof (ManagedDictionary);
magic = *managed->dictionary;
prepared = (const BrotliEncoderPreparedDictionary*)managed->dictionary;
}
if (magic == kPreparedDictionaryMagic) {
const PreparedDictionary* dictionary =
(const PreparedDictionary*)prepared;
/* Keep in sync with step 3 of CreatePreparedDictionary */
return sizeof (PreparedDictionary) + dictionary->source_size +
(sizeof (uint32_t) << dictionary->slot_bits) +
(sizeof (uint16_t) << dictionary->bucket_bits) +
(sizeof (uint32_t) * dictionary->num_items) + overhead;
} else if (magic == kLeanPreparedDictionaryMagic) {
const PreparedDictionary* dictionary =
(const PreparedDictionary*)prepared;
/* Keep in sync with step 3 of CreatePreparedDictionary */
return sizeof (PreparedDictionary) + sizeof (uint8_t*) +
(sizeof (uint32_t) << dictionary->slot_bits) +
(sizeof (uint16_t) << dictionary->bucket_bits) +
(sizeof (uint32_t) * dictionary->num_items) + overhead;
} else if (magic == kSharedDictionaryMagic) {
const SharedEncoderDictionary* dictionary =
(const SharedEncoderDictionary*)prepared;
const CompoundDictionary* compound = &dictionary->compound;
const ContextualEncoderDictionary* contextual = &dictionary->contextual;
size_t result = sizeof (*dictionary);
size_t i;
size_t num_instances;
const BrotliEncoderDictionary* instances;
for (i = 0 ; i < compound->num_prepared_instances_; i++) {
size_t size = BrotliEncoderGetPreparedDictionarySize(
(const BrotliEncoderPreparedDictionary*)
compound->prepared_instances_[i]);
if (!size) return 0 ; /* error */
result += size;
}
if (contextual->context_based) {
num_instances = contextual->num_instances_;
instances = contextual->instances_;
result += sizeof (*instances) * num_instances;
} else {
num_instances = 1 ;
instances = &contextual->instance_;
}
for (i = 0 ; i < num_instances; i++) {
const BrotliEncoderDictionary* dict = &instances[i];
result += dict->trie.pool_capacity * sizeof (BrotliTrieNode);
if (dict->hash_table_data_words_) {
result += sizeof (kStaticDictionaryHashWords);
}
if (dict->hash_table_data_lengths_) {
result += sizeof (kStaticDictionaryHashLengths);
}
if (dict->buckets_data_) {
result += sizeof (*dict->buckets_data_) * dict->buckets_alloc_size_;
}
if (dict->dict_words_data_) {
result += sizeof (*dict->dict_words) * dict->dict_words_alloc_size_;
}
if (dict->words_instance_) {
result += sizeof (*dict->words_instance_);
/* data_size not added here: it is never allocated by the
SharedEncoderDictionary , instead it always points to the file
already loaded in memory . So if the caller wants to include
this memory as well , add the size of the loaded dictionary
file to this. */
}
}
return result + overhead;
}
return 0 ; /* error */
}
#if defined (BROTLI_TEST)
size_t BrotliMakeUncompressedStreamForTest(const uint8_t*, size_t, uint8_t*);
size_t BrotliMakeUncompressedStreamForTest(
const uint8_t* input, size_t input_size, uint8_t* output) {
return MakeUncompressedStream(input, input_size, output);
}
#endif
#if defined (__cplusplus) || defined (c_plusplus)
} /* extern "C" */
#endif
Messung V0.5 in Prozent C=91 H=95 G=92
¤ Dauer der Verarbeitung: 0.36 Sekunden
¤
*© Formatika GbR, Deutschland