Make decompression routines optionally provide the actual uncompressd size

This commit is contained in:
Eric Biggers 2016-01-23 00:58:20 -06:00
parent e731f4b510
commit 7be1effacc
7 changed files with 91 additions and 55 deletions

View File

@ -11,7 +11,7 @@ The supported formats are:
- gzip (a.k.a. DEFLATE with a gzip wrapper) - gzip (a.k.a. DEFLATE with a gzip wrapper)
libdeflate is heavily optimized. It is significantly faster than the zlib libdeflate is heavily optimized. It is significantly faster than the zlib
software library, both for compression and decompression, and especially on x86 library, both for compression and decompression, and especially on x86
processors. In addition, libdeflate provides optional high compression modes processors. In addition, libdeflate provides optional high compression modes
that provide a better compression ratio than the zlib's "level 9". that provide a better compression ratio than the zlib's "level 9".
@ -28,7 +28,7 @@ There are various options which can be set on the `make` command line; see the
Makefile for details. As an example, you can run `make SUPPORT_COMPRESSION=no` Makefile for details. As an example, you can run `make SUPPORT_COMPRESSION=no`
to build a decompression-only library. to build a decompression-only library.
There is no `make install` yet; just copy the file(s) you want. There is no `make install` yet; just copy the file(s) to where you want.
It's possible to build a Windows binary using MinGW, using a command like this: It's possible to build a Windows binary using MinGW, using a command like this:
@ -50,6 +50,12 @@ certain use cases such as transparent filesystem compression. But if your
application compresses large files as a single compressed stream, similarly to application compresses large files as a single compressed stream, similarly to
the `gzip` program, then libdeflate isn't for you. the `gzip` program, then libdeflate isn't for you.
Note that with chunk-based compression, you generally should have the
uncompressed size of each chunk stored outside of the compressed data itself.
This enables you to allocate an output buffer of the correct size without
guessing. However, libdeflate's decompression routines do optionally provide
the actual number of output bytes in case you need it.
DEFLATE vs. zlib vs. gzip DEFLATE vs. zlib vs. gzip
========================= =========================
@ -87,10 +93,10 @@ compressed. Different algorithms and different amounts of computation time will
result in different compression ratios, while remaining equally compatible with result in different compression ratios, while remaining equally compatible with
the decompressor. the decompressor.
For this reason, the commonly used zlib software library provides nine For this reason, the commonly used zlib library provides nine compression
compression levels. Level 1 is the fastest but provides the worst compression; levels. Level 1 is the fastest but provides the worst compression; level 9
level 9 provides the best compression but is the slowest. It defaults to level provides the best compression but is the slowest. It defaults to level 6.
6. libdeflate uses this same design but is designed to improve on both zlib's libdeflate uses this same design but is designed to improve on both zlib's
performance *and* compression ratio at every compression level. In addition, performance *and* compression ratio at every compression level. In addition,
libdeflate's levels go [up to 12](https://xkcd.com/670/) to make room for a libdeflate's levels go [up to 12](https://xkcd.com/670/) to make room for a
minimum-cost-path based algorithm (sometimes called "optimal parsing") that can minimum-cost-path based algorithm (sometimes called "optimal parsing") that can
@ -102,8 +108,8 @@ different levels to see which works best for your application.
Motivation Motivation
========== ==========
Despite DEFLATE's widespread use mainly through the zlib software library, in Despite DEFLATE's widespread use mainly through the zlib library, in the
the compression community this format from the early 1990s is often considered compression community this format from the early 1990s is often considered
obsolete. And in a few significant ways, it is. obsolete. And in a few significant ways, it is.
So why implement DEFLATE at all, instead of focusing entirely on So why implement DEFLATE at all, instead of focusing entirely on
@ -119,8 +125,8 @@ optimizations (e.g. those dealing with LZ77 matchfinding) can be reused.
In addition, comparing compressors fairly is difficult because the performance In addition, comparing compressors fairly is difficult because the performance
of a compressor depends heavily on optimizations which are not intrinsic to the of a compressor depends heavily on optimizations which are not intrinsic to the
compression format itself. In this respect, the zlib software library sometimes compression format itself. In this respect, the zlib library sometimes compares
compares poorly to certain newer code because zlib is not well optimized for poorly to certain newer code because zlib is not well optimized for modern
modern processors. libdeflate addresses this by providing an optimized DEFLATE processors. libdeflate addresses this by providing an optimized DEFLATE
implementation which can be used for benchmarking purposes. And, of course, implementation which can be used for benchmarking purposes. And, of course,
real applications can use it as well. real applications can use it as well.

View File

@ -90,19 +90,20 @@ deflate_alloc_decompressor(void);
/* /*
* deflate_decompress() decompresses 'in_nbytes' bytes of DEFLATE-compressed * deflate_decompress() decompresses 'in_nbytes' bytes of DEFLATE-compressed
* data at 'in' and writes the uncompressed data, which had original size * data at 'in' and writes the uncompressed data to 'out', which is a buffer of
* 'out_nbytes', to 'out'. The return value is true if decompression was * at least 'out_nbytes_avail' bytes. If decompression was successful, then
* successful, or false if the compressed data was invalid. * %true is returned; otherwise, the compressed data must have been invalid and
* * %false is returned. In addition, on success, if 'actual_out_nbytes_ret' is
* Note that the uncompressed size must be known *exactly* and passed as * not NULL, then the actual uncompressed size is written to
* 'out_nbytes'. This is because this API is designed for block-based * *actual_out_nbytes_ret. Or, if 'actual_out_nbytes_ret' is NULL, then the
* compression where the uncompressed size should have already been stored * uncompressed size must be exactly equal to 'out_nbytes_avail'; otherwise
* elsewhere. * decompression fails and %false is returned.
*/ */
extern bool extern bool
deflate_decompress(struct deflate_decompressor *decompressor, deflate_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes); void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret);
/* /*
* Like deflate_decompress(), but assumes the zlib wrapper format instead of raw * Like deflate_decompress(), but assumes the zlib wrapper format instead of raw
@ -111,7 +112,8 @@ deflate_decompress(struct deflate_decompressor *decompressor,
extern bool extern bool
zlib_decompress(struct deflate_decompressor *decompressor, zlib_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes); void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret);
/* /*
* Like deflate_decompress(), but assumes the gzip wrapper format instead of raw * Like deflate_decompress(), but assumes the gzip wrapper format instead of raw
@ -120,7 +122,8 @@ zlib_decompress(struct deflate_decompressor *decompressor,
extern bool extern bool
gzip_decompress(struct deflate_decompressor *decompressor, gzip_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes); void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret);
/* /*
* deflate_free_decompressor() frees a DEFLATE decompressor that was allocated * deflate_free_decompressor() frees a DEFLATE decompressor that was allocated

View File

@ -9,10 +9,11 @@
static bool ATTRIBUTES static bool ATTRIBUTES
FUNCNAME(struct deflate_decompressor * restrict d, FUNCNAME(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes) void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
u8 *out_next = out; u8 *out_next = out;
u8 * const out_end = out_next + out_nbytes; u8 * const out_end = out_next + out_nbytes_avail;
const u8 *in_next = in; const u8 *in_next = in;
const u8 * const in_end = in_next + in_nbytes; const u8 * const in_end = in_next + in_nbytes;
bitbuf_t bitbuf = 0; bitbuf_t bitbuf = 0;
@ -358,7 +359,11 @@ block_done:
if (!is_final_block) if (!is_final_block)
goto next_block; goto next_block;
/* That was the last block. Return %true if we got all the output we /* That was the last block. */
* expected, otherwise %false. */
return (out_next == out_end); if (!actual_out_nbytes_ret)
return out_next == out_end;
*actual_out_nbytes_ret = out_next - (u8 *)out;
return true;
} }

View File

@ -817,18 +817,21 @@ copy_word_unaligned(const void *src, void *dst)
static bool static bool
dispatch(struct deflate_decompressor * restrict d, dispatch(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes); void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret);
typedef bool (*decompress_func_t)(struct deflate_decompressor * restrict d, typedef bool (*decompress_func_t)(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes); void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret);
static decompress_func_t decompress_impl = dispatch; static decompress_func_t decompress_impl = dispatch;
static bool static bool
dispatch(struct deflate_decompressor * restrict d, dispatch(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes) void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
decompress_func_t f = deflate_decompress_default; decompress_func_t f = deflate_decompress_default;
#if X86_CPU_FEATURES_ENABLED #if X86_CPU_FEATURES_ENABLED
@ -836,32 +839,33 @@ dispatch(struct deflate_decompressor * restrict d,
f = deflate_decompress_bmi2; f = deflate_decompress_bmi2;
#endif #endif
decompress_impl = f; decompress_impl = f;
return (*f)(d, in, in_nbytes, out, out_nbytes); return (*f)(d, in, in_nbytes, out, out_nbytes_avail,
actual_out_nbytes_ret);
} }
#endif /* DISPATCH_ENABLED */ #endif /* DISPATCH_ENABLED */
/* /*
* This is the main DEFLATE decompression routine. It decompresses 'in_nbytes' * This is the main DEFLATE decompression routine. See libdeflate.h for the
* bytes of compressed data from the buffer 'in' and writes the uncompressed * documentation.
* data to the buffer 'out'. The caller must know the exact length of the
* uncompressed data and pass it as 'out_nbytes'. The return value is %true if
* and only if decompression was successful. A return value of %false indicates
* that either the compressed data is invalid or it does not decompress to
* exactly 'out_nbytes' bytes of uncompressed data.
* *
* The real code is in decompress_impl.h. The part here just handles calling * Note that the real code is in decompress_impl.h. The part here just handles
* the appropriate implementation depending on the CPU features at runtime. * calling the appropriate implementation depending on the CPU features at
* runtime.
*/ */
LIBEXPORT bool LIBEXPORT bool
deflate_decompress(struct deflate_decompressor * restrict d, deflate_decompress(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes) void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
#if DISPATCH_ENABLED #if DISPATCH_ENABLED
return (*decompress_impl)(d, in, in_nbytes, out, out_nbytes); return (*decompress_impl)(d, in, in_nbytes, out, out_nbytes_avail,
actual_out_nbytes_ret);
#else #else
return deflate_decompress_default(d, in, in_nbytes, out, out_nbytes); return deflate_decompress_default(d, in, in_nbytes, out,
out_nbytes_avail,
actual_out_nbytes_ret);
#endif #endif
} }

View File

@ -16,11 +16,14 @@
LIBEXPORT bool LIBEXPORT bool
gzip_decompress(struct deflate_decompressor *d, gzip_decompress(struct deflate_decompressor *d,
const void *in, size_t in_nbytes, void *out, size_t out_nbytes) const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
const u8 *in_next = in; const u8 *in_next = in;
const u8 * const in_end = in_next + in_nbytes; const u8 * const in_end = in_next + in_nbytes;
u8 flg; u8 flg;
size_t actual_out_nbytes;
if (in_nbytes < GZIP_MIN_OVERHEAD) if (in_nbytes < GZIP_MIN_OVERHEAD)
return false; return false;
@ -81,18 +84,23 @@ gzip_decompress(struct deflate_decompressor *d,
/* Compressed data */ /* Compressed data */
if (!deflate_decompress(d, in_next, in_end - GZIP_FOOTER_SIZE - in_next, if (!deflate_decompress(d, in_next, in_end - GZIP_FOOTER_SIZE - in_next,
out, out_nbytes)) out, out_nbytes_avail, actual_out_nbytes_ret))
return false; return false;
if (actual_out_nbytes_ret)
actual_out_nbytes = *actual_out_nbytes_ret;
else
actual_out_nbytes = out_nbytes_avail;
in_next = in_end - GZIP_FOOTER_SIZE; in_next = in_end - GZIP_FOOTER_SIZE;
/* CRC32 */ /* CRC32 */
if (crc32_gzip(out, out_nbytes) != get_unaligned_le32(in_next)) if (crc32_gzip(out, actual_out_nbytes) != get_unaligned_le32(in_next))
return false; return false;
in_next += 4; in_next += 4;
/* ISIZE */ /* ISIZE */
if ((u32)out_nbytes != get_unaligned_le32(in_next)) if (actual_out_nbytes != get_unaligned_le32(in_next))
return false; return false;
return true; return true;

View File

@ -16,11 +16,14 @@
LIBEXPORT bool LIBEXPORT bool
zlib_decompress(struct deflate_decompressor *d, zlib_decompress(struct deflate_decompressor *d,
const void *in, size_t in_nbytes, void *out, size_t out_nbytes) const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
const u8 *in_next = in; const u8 *in_next = in;
const u8 * const in_end = in_next + in_nbytes; const u8 * const in_end = in_next + in_nbytes;
u16 hdr; u16 hdr;
size_t actual_out_nbytes;
if (in_nbytes < ZLIB_MIN_OVERHEAD) if (in_nbytes < ZLIB_MIN_OVERHEAD)
return false; return false;
@ -47,13 +50,18 @@ zlib_decompress(struct deflate_decompressor *d,
/* Compressed data */ /* Compressed data */
if (!deflate_decompress(d, in_next, in_end - ZLIB_FOOTER_SIZE - in_next, if (!deflate_decompress(d, in_next, in_end - ZLIB_FOOTER_SIZE - in_next,
out, out_nbytes)) out, out_nbytes_avail, actual_out_nbytes_ret))
return false; return false;
if (actual_out_nbytes_ret)
actual_out_nbytes = *actual_out_nbytes_ret;
else
actual_out_nbytes = out_nbytes_avail;
in_next = in_end - ZLIB_FOOTER_SIZE; in_next = in_end - ZLIB_FOOTER_SIZE;
/* ADLER32 */ /* ADLER32 */
if (adler32(out, out_nbytes) != get_unaligned_be32(in_next)) if (adler32(out, actual_out_nbytes) != get_unaligned_be32(in_next))
return false; return false;
return true; return true;

View File

@ -181,13 +181,14 @@ compressor_destroy(struct compressor *c)
struct decompressor { struct decompressor {
void *private; void *private;
bool (*decompress)(void *, const void *, size_t, void *, size_t); bool (*decompress)(void *, const void *, size_t, void *, size_t, size_t *);
void (*free_private)(void *); void (*free_private)(void *);
}; };
static bool static bool
libz_decompress(void *private, const void *in, size_t in_nbytes, libz_decompress(void *private, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes) void *out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret)
{ {
z_stream *z = private; z_stream *z = private;
@ -196,7 +197,7 @@ libz_decompress(void *private, const void *in, size_t in_nbytes,
z->next_in = (void *)in; z->next_in = (void *)in;
z->avail_in = in_nbytes; z->avail_in = in_nbytes;
z->next_out = out; z->next_out = out;
z->avail_out = out_nbytes; z->avail_out = out_nbytes_avail;
return (inflate(z, Z_FINISH) == Z_STREAM_END && z->avail_out == 0); return (inflate(z, Z_FINISH) == Z_STREAM_END && z->avail_out == 0);
} }
@ -250,9 +251,10 @@ decompressor_init(struct decompressor *d, enum wrapper wrapper, bool use_libz)
static bool static bool
do_decompress(struct decompressor *d, const void *in, size_t in_nbytes, do_decompress(struct decompressor *d, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes) void *out, size_t out_nbytes_avail)
{ {
return (*d->decompress)(d->private, in, in_nbytes, out, out_nbytes); return (*d->decompress)(d->private, in, in_nbytes,
out, out_nbytes_avail, NULL);
} }
static void static void