Windows2000/private/inet/urlmon/compress/gzip/deflate.c
2020-09-30 17:12:32 +02:00

433 lines
14 KiB
C

/*
* deflate.c
* Main compression entrypoint for all three encoders
*/
#include <string.h>
#include <stdio.h>
#include <crtdbg.h>
#include "deflate.h"
#include "fasttbl.h"
#include "defgzip.h"
typedef struct config_s
{
int good_length; /* reduce lazy search above this match length */
int max_lazy; /* do not perform lazy search above this match length */
int nice_length; /* quit search above this match length */
int max_chain;
} compression_config;
static const compression_config configuration_table[11] = {
/* good lazy nice chain */
/* 0 */ {0, 0, 0, 0 }, /* store only */
/* 1 */ {4, 4, 8, 4 }, /* maximum speed, no lazy matches */
/* 2 */ {4, 5, 16, 8 },
/* 3 */ {4, 6, 32, 32 },
/* 4 */ {4, 4, 16, 16 }, /* lazy matches */
/* 5 */ {8, 16, 32, 32 },
/* 6 */ {8, 16, 128, 128 },
/* 7 */ {8, 32, 128, 256 },
/* 8 */ {32, 128, 258, 1024 },
/* 9 */ {32, 258, 258, 4096 },
/* 10 */ {32, 258, 258, 4096 } /* maximum compression */
};
// Destroy the std encoder, optimal encoder, and fast encoder, but leave the
// compressor context around
VOID DestroyIndividualCompressors(PVOID void_context)
{
t_encoder_context *context = (t_encoder_context *) void_context;
if (context->std_encoder != NULL)
{
LocalFree((PVOID) context->std_encoder);
context->std_encoder = NULL;
}
if (context->optimal_encoder != NULL)
{
LocalFree((PVOID) context->optimal_encoder);
context->optimal_encoder = NULL;
}
if (context->fast_encoder != NULL)
{
LocalFree((PVOID) context->fast_encoder);
context->fast_encoder = NULL;
}
}
// Mark the final block in the compressed data
// There must be one final block with bfinal=1 indicating that it is the last one. In the case of
// the fast encoder we just need to output the end of block code, since the fast encoder just outputs
// one very long block.
// In the case of the standard and optimal encoders we have already finished outputting blocks,
// so we output a new block (a static/fixed block) with bfinal=1, consisting merely of the
// end of block code.
static void markFinalBlock(t_encoder_context *context)
{
if (context->fast_encoder != NULL)
{
// The fast encoder outputs one long block, so it just needs to terminate this block
outputBits(
context,
g_FastEncoderLiteralTreeLength[END_OF_BLOCK_CODE],
g_FastEncoderLiteralTreeCode[END_OF_BLOCK_CODE]
);
}
else
{
// To finish, output a static block consisting of a single end of block code
// Combined these three outputBits() calls (commented out) into one call
// The total number of bits output in one shot must be <= 16, but we're ok
// since the the length of END_OF_BLOCK_CODE is 7 for a static (fixed) block
#if 0
outputBits(context, 1, 1); // bfinal = 1
outputBits(context, 2, BLOCKTYPE_FIXED);
outputBits(context, g_StaticLiteralTreeLength[END_OF_BLOCK_CODE], g_StaticLiteralTreeCode[END_OF_BLOCK_CODE]);
#endif
// note: g_StaticLiteralTreeCode[END_OF_BLOCK_CODE] == 0x0000
outputBits(
context,
(7 + 3), // StaticLiteralTreeLength[END_OF_BLOCK_CODE]=7, + 1 bfinal bit + 2 blocktype bits
((0x0000) << 3) | (BLOCKTYPE_FIXED << 1) | 1
);
}
// flush bits from bit buffer to output buffer
flushOutputBitBuffer(context);
if (context->using_gzip)
WriteGzipFooter(context);
}
// Returns a pointer to the start of the window of the currently active compressor
// Used for memcpy'ing window data when we reach the end of the window
static BYTE *GetEncoderWindow(t_encoder_context *context)
{
_ASSERT(context->std_encoder != NULL || context->optimal_encoder != NULL || context->fast_encoder != NULL);
if (context->std_encoder != NULL)
return context->std_encoder->window;
else if (context->optimal_encoder != NULL)
return context->optimal_encoder->window;
else
return context->fast_encoder->window;
}
// This function does the actual work of resetting the compression state.
// However, it does not free the std/fast/optimal encoder memory (something
// that the external ResetCompression() API currently does).
void InternalResetCompression(t_encoder_context *context)
{
context->no_more_input = FALSE;
context->marked_final_block = FALSE;
context->state = STATE_NORMAL;
context->outputting_block_num_literals = 0;
if (context->using_gzip)
EncoderInitGzipVariables(context);
InitBitBuffer(context);
}
// The compress API
HRESULT WINAPI Compress(
PVOID void_context,
CONST BYTE * input_buffer,
LONG input_buffer_size,
PBYTE output_buffer,
LONG output_buffer_size,
PLONG input_used,
PLONG output_used,
INT compression_level
)
{
int lazy_match_threshold;
int search_depth;
int good_length;
int nice_length;
t_encoder_context * context = (t_encoder_context *) void_context;
t_std_encoder * std_encoder;
t_optimal_encoder * optimal_encoder;
t_fast_encoder * fast_encoder;
HRESULT result = S_OK; // default to success
*input_used = 0;
*output_used = 0;
// validate compression level
if (compression_level < 0 || compression_level > 10)
{
result = E_INVALIDARG;
goto exit;
}
context->output_curpos = output_buffer;
context->output_endpos = output_buffer + output_buffer_size;
context->output_near_end_threshold = output_buffer + output_buffer_size - 16;
// Have we allocated the particular compressor we want yet?
if (context->std_encoder == NULL && context->optimal_encoder == NULL && context->fast_encoder == NULL)
{
// No
if (compression_level <= 3) // fast encoder
{
if (FastEncoderInit(context) == FALSE)
{
result = E_OUTOFMEMORY;
goto exit;
}
}
else if (compression_level == 10) // optimal encoder
{
if (OptimalEncoderInit(context) == FALSE)
{
result = E_OUTOFMEMORY;
goto exit;
}
}
else
{
if (StdEncoderInit(context) == FALSE)
{
result = E_OUTOFMEMORY;
goto exit;
}
}
}
std_encoder = context->std_encoder;
optimal_encoder = context->optimal_encoder;
fast_encoder = context->fast_encoder;
_ASSERT(std_encoder != NULL || optimal_encoder != NULL || fast_encoder != NULL);
// set search depth
if (fast_encoder != NULL)
{
search_depth = configuration_table[compression_level].max_chain;
good_length = configuration_table[compression_level].good_length;
nice_length = configuration_table[compression_level].nice_length;
lazy_match_threshold = configuration_table[compression_level].max_lazy;
}
else if (std_encoder != NULL)
{
search_depth = configuration_table[compression_level].max_chain;
good_length = configuration_table[compression_level].good_length;
nice_length = configuration_table[compression_level].nice_length;
lazy_match_threshold = configuration_table[compression_level].max_lazy;
}
// the output buffer must be large enough to contain an entire tree
if (output_buffer_size < MAX_TREE_DATA_SIZE)
{
result = E_INVALIDARG;
goto exit;
}
if (context->using_gzip && context->gzip_fOutputGzipHeader == FALSE)
{
// Write the GZIP header
WriteGzipHeader(context, compression_level);
context->gzip_fOutputGzipHeader = TRUE;
}
// Check if previously we were in the middle of outputting a block
if (context->state != STATE_NORMAL)
{
// The fast encoder is a special case; it doesn't use OutputBlock()
if (fast_encoder != NULL)
goto start_encoding;
// yes we were, so continue outputting it
OutputBlock(context);
// Check if we're still outputting a block (it may be a long block that
// has filled up the output buffer again)
// If we're coming close to the end of the buffer, and may not have enough space to
// output a full tree structure, stop now.
if (context->state != STATE_NORMAL ||
context->output_endpos - context->output_curpos < MAX_TREE_DATA_SIZE)
{
*output_used = (long) (context->output_curpos - output_buffer);
goto set_output_used_then_exit; // success
}
// We finished outputting the previous block, so time to compress some more input
}
#ifdef _DEBUG
// Fast encoder doesn't use outputBlock, so it doesn't have the tree limitation
if (fast_encoder == NULL)
_ASSERTE(context->output_endpos - context->output_curpos >= MAX_TREE_DATA_SIZE);
#endif
// input_buffer_size == 0 means "this is the final block"
// Of course, the client may still need to call Compress() many more times if the output
// buffer is small and there is a big block waiting to be sent.
// We may even have some pending input data in our buffer waiting to be compressed.
if ((input_buffer_size == 0 || context->no_more_input) && context->bufpos >= context->bufpos_end)
{
// if we're ever passed zero bytes of input, it means that there will never be any
// more input
context->no_more_input = TRUE;
// output existing block
// this never happens for the fast encoder, since we don't record blocks
if (context->outputting_block_num_literals != 0)
{
FlushRecordingBuffer(context);
OutputBlock(context);
// Still outputting a block?
if (context->state != STATE_NORMAL)
goto set_output_used_then_exit; // success
}
// for the fast encoder only, we won't have output our fast encoder preamble if the
// file size == 0, so output it now if we haven't already.
if (fast_encoder != NULL)
{
if (fast_encoder->fOutputBlockHeader == FALSE)
{
fast_encoder->fOutputBlockHeader = TRUE;
FastEncoderOutputPreamble(context);
}
}
// if we've already marked the final block, don't do it again
if (context->marked_final_block)
{
result = S_FALSE;
goto set_output_used_then_exit; // should be zero output used
}
// ensure there is enough space to output the final block (max 8 bytes)
if (context->output_curpos + 8 >= context->output_endpos)
goto set_output_used_then_exit; // not enough space - do it next time
// output the final block (of length zero - we just want the bfinal=1 marker)
markFinalBlock(context);
context->marked_final_block = TRUE;
result = S_FALSE;
goto set_output_used_then_exit;
}
// while there is more input data (passed in as parameters) or existing data in
// the window to compress
start_encoding:
while ((input_buffer_size > 0) || (context->bufpos < context->bufpos_end))
{
long amount_to_compress;
long window_space_available;
_ASSERT(context->bufpos >= context->window_size && context->bufpos < (2*context->window_size));
#ifdef _DEBUG
// Fast encoder doesn't use outputBlock, so it doesn't have the tree limitation
if (fast_encoder == NULL)
_ASSERTE(context->output_endpos - context->output_curpos >= MAX_TREE_DATA_SIZE);
#endif
// read more input data into the window if there is space available
window_space_available = (2*context->window_size) - context->bufpos_end;
amount_to_compress = (input_buffer_size < window_space_available) ? input_buffer_size : window_space_available;
if (amount_to_compress > 0)
{
*input_used += amount_to_compress;
// copy data into history window
if (context->using_gzip)
{
// In addition to copying data into the history window, GZIP wants a crc32 of the input data.
// We will do both of these things at the same time for the purposes of data locality,
// performance etc.
GzipCRCmemcpy(context, GetEncoderWindow(context) + context->bufpos_end, input_buffer, amount_to_compress);
}
else
{
// Copy data into history window
memcpy(GetEncoderWindow(context) + context->bufpos_end, input_buffer, amount_to_compress);
}
input_buffer += amount_to_compress;
input_buffer_size -= amount_to_compress;
// last input location
context->bufpos_end += amount_to_compress;
}
if (optimal_encoder != NULL)
OptimalEncoderDeflate(context);
else if (std_encoder != NULL)
StdEncoderDeflate(context, search_depth, lazy_match_threshold, good_length, nice_length);
else if (fast_encoder != NULL)
FastEncoderDeflate(context, search_depth, lazy_match_threshold, good_length, nice_length);
// either we reached the end of the buffer, or we had to output a block and ran out
// of output space midway
_ASSERT(context->bufpos == context->bufpos_end || context->state != STATE_NORMAL);
// if we ran out of output space, break now
if (context->state != STATE_NORMAL)
break;
// another check for running out of output space
if (fast_encoder == NULL && context->output_endpos - context->output_curpos >= MAX_TREE_DATA_SIZE)
break;
} /* end ... while (input_buffer_size > 0) */
set_output_used_then_exit:
*output_used = (long) (context->output_curpos - output_buffer);
exit:
_ASSERT(*output_used < output_buffer_size); // make sure we didn't overflow the output buffer
_ASSERT(context->bufpos >= context->window_size && context->bufpos <= 2*context->window_size); // make sure bufpos is sane
return result;
}