diff --git a/Makefile b/Makefile index 3e9402c..f6d9f17 100644 --- a/Makefile +++ b/Makefile @@ -216,7 +216,8 @@ TEST_PROG_SRC := programs/benchmark.c \ programs/checksum.c \ programs/test_checksums.c \ programs/test_incomplete_codes.c \ - programs/test_slow_decompression.c + programs/test_slow_decompression.c \ + programs/test_zlib.c NONTEST_PROGRAMS := $(NONTEST_PROG_SRC:programs/%.c=%$(PROG_SUFFIX)) DEFAULT_TARGETS += $(NONTEST_PROGRAMS) diff --git a/lib/zlib_decompress.c b/lib/zlib_decompress.c index 621197e..0f6c714 100644 --- a/lib/zlib_decompress.c +++ b/lib/zlib_decompress.c @@ -33,14 +33,16 @@ #include "libdeflate.h" LIBDEFLATEEXPORT enum libdeflate_result LIBDEFLATEAPI -libdeflate_zlib_decompress(struct libdeflate_decompressor *d, - const void *in, size_t in_nbytes, - void *out, size_t out_nbytes_avail, - size_t *actual_out_nbytes_ret) +libdeflate_zlib_decompress_ex(struct libdeflate_decompressor *d, + const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret, + size_t *actual_out_nbytes_ret) { const u8 *in_next = in; const u8 * const in_end = in_next + in_nbytes; u16 hdr; + size_t actual_in_nbytes; size_t actual_out_nbytes; enum libdeflate_result result; @@ -68,10 +70,10 @@ libdeflate_zlib_decompress(struct libdeflate_decompressor *d, return LIBDEFLATE_BAD_DATA; /* Compressed data */ - result = libdeflate_deflate_decompress(d, in_next, + result = libdeflate_deflate_decompress_ex(d, in_next, in_end - ZLIB_FOOTER_SIZE - in_next, out, out_nbytes_avail, - actual_out_nbytes_ret); + &actual_in_nbytes, actual_out_nbytes_ret); if (result != LIBDEFLATE_SUCCESS) return result; @@ -80,12 +82,27 @@ libdeflate_zlib_decompress(struct libdeflate_decompressor *d, else actual_out_nbytes = out_nbytes_avail; - in_next = in_end - ZLIB_FOOTER_SIZE; + in_next += actual_in_nbytes; /* ADLER32 */ if (libdeflate_adler32(1, out, actual_out_nbytes) != get_unaligned_be32(in_next)) return LIBDEFLATE_BAD_DATA; + in_next += 4; + + if (actual_in_nbytes_ret) + *actual_in_nbytes_ret = in_next - (u8 *)in; return LIBDEFLATE_SUCCESS; } + +LIBDEFLATEEXPORT enum libdeflate_result LIBDEFLATEAPI +libdeflate_zlib_decompress(struct libdeflate_decompressor *d, + const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail, + size_t *actual_out_nbytes_ret) +{ + return libdeflate_zlib_decompress_ex(d, in, in_nbytes, + out, out_nbytes_avail, + NULL, actual_out_nbytes_ret); +} diff --git a/libdeflate.h b/libdeflate.h index 10fb187..27d8e87 100644 --- a/libdeflate.h +++ b/libdeflate.h @@ -255,6 +255,10 @@ libdeflate_deflate_decompress_ex(struct libdeflate_decompressor *decompressor, /* * Like libdeflate_deflate_decompress(), but assumes the zlib wrapper format * instead of raw DEFLATE. + * + * Decompression will stop at the end of the zlib stream, even if it is shorter + * than 'in_nbytes'. If you need to know exactly where the zlib stream ended, + * use libdeflate_zlib_decompress_ex(). */ LIBDEFLATEEXPORT enum libdeflate_result LIBDEFLATEAPI libdeflate_zlib_decompress(struct libdeflate_decompressor *decompressor, @@ -262,6 +266,20 @@ libdeflate_zlib_decompress(struct libdeflate_decompressor *decompressor, void *out, size_t out_nbytes_avail, size_t *actual_out_nbytes_ret); +/* + * Like libdeflate_zlib_decompress(), but adds the 'actual_in_nbytes_ret' + * argument. If 'actual_in_nbytes_ret' is not NULL and the decompression + * succeeds (indicating that the first zlib-compressed stream in the input + * buffer was decompressed), then the actual number of input bytes consumed is + * written to *actual_in_nbytes_ret. + */ +LIBDEFLATEEXPORT enum libdeflate_result LIBDEFLATEAPI +libdeflate_zlib_decompress_ex(struct libdeflate_decompressor *decompressor, + const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail, + size_t *actual_in_nbytes_ret, + size_t *actual_out_nbytes_ret); + /* * Like libdeflate_deflate_decompress(), but assumes the gzip wrapper format * instead of raw DEFLATE. diff --git a/programs/test_zlib.c b/programs/test_zlib.c new file mode 100644 index 0000000..706d4bb --- /dev/null +++ b/programs/test_zlib.c @@ -0,0 +1,106 @@ +/* + * test_zlib.c + * + * Verify that libdeflate_zlib_decompress and libdeflate_zlib_decompress_ex can + * correctly decompress the results of libdeflate_zlib_compress. Also checks + * whether decompression correctly handles additional trailing bytes in the + * compressed buffer. + */ + +#include + +#include "test_util.h" + +static void +test_decompress(u8 const* compressed, size_t compressed_nbytes, + u8 const* expected, size_t expected_nbytes) +{ + struct libdeflate_decompressor* d = libdeflate_alloc_decompressor(); + ASSERT(d != NULL); + + size_t decompressed_nbytes = expected_nbytes; + u8 *decompressed = xmalloc(decompressed_nbytes); + + size_t actual_decompressed_nbytes = 0; + enum libdeflate_result res = + libdeflate_zlib_decompress(d, compressed, compressed_nbytes, + decompressed, decompressed_nbytes, + &actual_decompressed_nbytes); + ASSERT(res == LIBDEFLATE_SUCCESS); + ASSERT(actual_decompressed_nbytes == expected_nbytes); + ASSERT(memcmp(expected, decompressed, expected_nbytes) == 0); + + free(decompressed); + libdeflate_free_decompressor(d); +} + +static void +test_decompress_ex(u8 const* compressed, size_t compressed_nbytes, + size_t expected_actual_compressed_nbytes, + u8 const* expected, size_t expected_nbytes) +{ + struct libdeflate_decompressor* d = libdeflate_alloc_decompressor(); + ASSERT(d != NULL); + + size_t decompressed_nbytes = expected_nbytes; + u8 *decompressed = xmalloc(decompressed_nbytes); + + size_t actual_compressed_nbytes = 0; + size_t actual_decompressed_nbytes = 0; + enum libdeflate_result res = + libdeflate_zlib_decompress_ex(d, compressed, compressed_nbytes, + decompressed, decompressed_nbytes, + &actual_compressed_nbytes, + &actual_decompressed_nbytes); + ASSERT(res == LIBDEFLATE_SUCCESS); + ASSERT(actual_compressed_nbytes == expected_actual_compressed_nbytes); + ASSERT(actual_decompressed_nbytes == expected_nbytes); + ASSERT(memcmp(expected, decompressed, expected_nbytes) == 0); + + free(decompressed); + libdeflate_free_decompressor(d); +} + +int +tmain(int argc, tchar *argv[]) +{ + program_invocation_name = get_filename(argv[0]); + + size_t original_nbytes = 32768; + u8 *original = xmalloc(original_nbytes); + + /* Prepare some dummy data to compress */ + for (size_t i = 0; i < original_nbytes; ++i) { + original[i] = (i % 123) + (i % 1023); + } + + size_t compressed_nbytes_total = 32768; + u8 *compressed = xmalloc(compressed_nbytes_total); + memset(compressed, 0x00, compressed_nbytes_total); + + /* + * Don't use the full buffer for compressed data, because we want to + * test whether decompression can deal with additional trailing bytes. + */ + size_t compressed_nbytes_avail = 30000; + ASSERT(compressed_nbytes_avail < compressed_nbytes_total); + + struct libdeflate_compressor* c = libdeflate_alloc_compressor(6); + ASSERT(c != NULL); + size_t compressed_nbytes = + libdeflate_zlib_compress(c, original, original_nbytes, + compressed, compressed_nbytes_avail); + ASSERT(compressed_nbytes > 0); + libdeflate_free_compressor(c); + + test_decompress(compressed, compressed_nbytes, original, original_nbytes); + test_decompress(compressed, compressed_nbytes_total, original, original_nbytes); + test_decompress_ex(compressed, compressed_nbytes_total, compressed_nbytes, + original, original_nbytes); + + printf("libdeflate_zlib_* tests passed!\n"); + + free(compressed); + free(original); + return 0; +}