summaryrefslogtreecommitdiffstats
path: root/tests/test_lzip_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_lzip_decoder.c')
-rw-r--r--tests/test_lzip_decoder.c475
1 files changed, 475 insertions, 0 deletions
diff --git a/tests/test_lzip_decoder.c b/tests/test_lzip_decoder.c
new file mode 100644
index 0000000..306de74
--- /dev/null
+++ b/tests/test_lzip_decoder.c
@@ -0,0 +1,475 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file test_lzip_decoder.c
+/// \brief Tests decoding lzip data
+//
+// Author: Jia Tan
+//
+// This file has been put into the public domain.
+// You can do whatever you want with this file.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tests.h"
+
+#ifdef HAVE_LZIP_DECODER
+
+// Memlimit large enough to pass all of the test files
+#define MEMLIMIT (1U << 20)
+#define DECODE_CHUNK_SIZE 1024
+
+
+// The uncompressed data in the test files are short US-ASCII strings.
+// The tests check if the decompressed output is what it is expected to be.
+// Storing the strings here as text would break the tests on EBCDIC systems
+// and storing the strings as an array of hex values is inconvenient, so
+// store the CRC32 values of the expected data instead.
+//
+// CRC32 value of "Hello\nWorld\n"
+static const uint32_t hello_world_crc = 0x15A2A343;
+
+// CRC32 value of "Trailing garbage\n"
+static const uint32_t trailing_garbage_crc = 0x87081A60;
+
+
+// Helper function to decode a good file with no flags and plenty high memlimit
+static void
+basic_lzip_decode(const char *src, const uint32_t expected_crc) {
+ size_t file_size;
+ uint8_t *data = tuktest_file_from_srcdir(src, &file_size);
+ uint32_t checksum = 0;
+
+ lzma_stream strm = LZMA_STREAM_INIT;
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0), LZMA_OK);
+
+ uint8_t *output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
+
+ strm.next_in = data;
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+
+ // Feed 1 byte at a time to the decoder to look for any bugs
+ // when switching between decoding sequences
+ lzma_ret ret = LZMA_OK;
+ while (ret == LZMA_OK) {
+ strm.avail_in = 1;
+ ret = lzma_code(&strm, LZMA_RUN);
+ if (strm.avail_out == 0) {
+ checksum = lzma_crc32(output_buffer,
+ strm.next_out - output_buffer,
+ checksum);
+ // No need to free output_buffer because it will
+ // automatically be freed at the end of the test by
+ // tuktest.
+ output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ }
+ }
+
+ assert_lzma_ret(ret, LZMA_STREAM_END);
+ assert_uint_eq(strm.total_in, file_size);
+
+ checksum = lzma_crc32(output_buffer, strm.next_out - output_buffer,
+ checksum);
+ assert_uint_eq(checksum, expected_crc);
+
+ lzma_end(&strm);
+}
+
+
+static void
+test_options(void)
+{
+ // Test NULL stream
+ assert_lzma_ret(lzma_lzip_decoder(NULL, MEMLIMIT, 0),
+ LZMA_PROG_ERROR);
+
+ // Test invalid flags
+ lzma_stream strm = LZMA_STREAM_INIT;
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, UINT32_MAX),
+ LZMA_OPTIONS_ERROR);
+ // Memlimit tests are done elsewhere
+}
+
+
+static void
+test_v0_decode(void) {
+ // This tests if liblzma can decode lzip version 0 files.
+ // lzip 1.17 and older can decompress this, but lzip 1.18
+ // and newer can no longer decode these files.
+ basic_lzip_decode("files/good-1-v0.lz", hello_world_crc);
+}
+
+
+static void
+test_v1_decode(void) {
+ // This tests decoding a basic lzip v1 file
+ basic_lzip_decode("files/good-1-v1.lz", hello_world_crc);
+}
+
+
+// Helper function to decode a good file with trailing bytes after
+// the lzip stream
+static void
+trailing_helper(const char *src, const uint32_t expected_data_checksum,
+ const uint32_t expected_trailing_checksum) {
+ size_t file_size;
+ uint32_t checksum = 0;
+ uint8_t *data = tuktest_file_from_srcdir(src, &file_size);
+ lzma_stream strm = LZMA_STREAM_INIT;
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ uint8_t *output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
+
+ strm.next_in = data;
+ strm.next_out = output_buffer;
+ strm.avail_in = file_size;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+
+ lzma_ret ret = LZMA_OK;
+ while (ret == LZMA_OK) {
+ ret = lzma_code(&strm, LZMA_RUN);
+ if (strm.avail_out == 0) {
+ checksum = lzma_crc32(output_buffer,
+ strm.next_out - output_buffer,
+ checksum);
+ // No need to free output_buffer because it will
+ // automatically be freed at the end of the test by
+ // tuktest.
+ output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ }
+ }
+
+ assert_lzma_ret(ret, LZMA_STREAM_END);
+ assert_uint(strm.total_in, <, file_size);
+
+ checksum = lzma_crc32(output_buffer,
+ strm.next_out - output_buffer,
+ checksum);
+
+ assert_uint_eq(checksum, expected_data_checksum);
+
+ // Trailing data should be readable from strm.next_in
+ checksum = lzma_crc32(strm.next_in, strm.avail_in, 0);
+ assert_uint_eq(checksum, expected_trailing_checksum);
+
+ lzma_end(&strm);
+}
+
+
+// Helper function to decode a bad file and compare to returned error to
+// what the caller expects
+static void
+decode_expect_error(const char *src, lzma_ret expected_error)
+{
+ lzma_stream strm = LZMA_STREAM_INIT;
+ size_t file_size;
+ uint8_t *data = tuktest_file_from_srcdir(src, &file_size);
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ uint8_t output_buffer[DECODE_CHUNK_SIZE];
+
+ strm.avail_in = file_size;
+ strm.next_in = data;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ lzma_ret ret = LZMA_OK;
+
+ while (ret == LZMA_OK) {
+ // Discard output since we are only looking for errors
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ if (strm.avail_in == 0)
+ ret = lzma_code(&strm, LZMA_FINISH);
+ else
+ ret = lzma_code(&strm, LZMA_RUN);
+ }
+
+ assert_lzma_ret(ret, expected_error);
+ lzma_end(&strm);
+}
+
+
+static void
+test_v0_trailing(void) {
+ trailing_helper("files/good-1-v0-trailing-1.lz", hello_world_crc,
+ trailing_garbage_crc);
+}
+
+
+static void
+test_v1_trailing(void) {
+ trailing_helper("files/good-1-v1-trailing-1.lz", hello_world_crc,
+ trailing_garbage_crc);
+
+ // The second files/good-1-v1-trailing-2.lz will have the same
+ // expected output and trailing output as
+ // files/good-1-v1-trailing-1.lz, but this tests if the prefix
+ // to the trailing data contains lzip magic bytes.
+ // When this happens, the expected behavior is to silently ignore
+ // the magic byte prefix and consume it from the input file.
+ trailing_helper("files/good-1-v1-trailing-2.lz", hello_world_crc,
+ trailing_garbage_crc);
+
+ // Expect LZMA_BUF error if a file ends with the lzip magic bytes
+ // but does not contain any data after
+ decode_expect_error("files/bad-1-v1-trailing-magic.lz",
+ LZMA_BUF_ERROR);
+}
+
+
+static void
+test_concatentated(void)
+{
+ // First test a file with one v0 member and one v1 member
+ // The first member should contain "Hello\n" and
+ // the second member should contain "World!\n"
+
+ lzma_stream strm = LZMA_STREAM_INIT;
+ size_t file_size;
+ uint8_t *v0_v1 = tuktest_file_from_srcdir("files/good-2-v0-v1.lz",
+ &file_size);
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ uint8_t output_buffer[DECODE_CHUNK_SIZE];
+
+ strm.avail_in = file_size;
+ strm.next_in = v0_v1;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
+
+ assert_uint_eq(strm.total_in, file_size);
+
+ uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0);
+ assert_uint_eq(checksum, hello_world_crc);
+
+ // The second file contains one v1 member and one v2 member
+ uint8_t *v1_v0 = tuktest_file_from_srcdir("files/good-2-v1-v0.lz",
+ &file_size);
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ strm.avail_in = file_size;
+ strm.next_in = v1_v0;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
+
+ assert_uint_eq(strm.total_in, file_size);
+ checksum = lzma_crc32(output_buffer, strm.total_out, 0);
+ assert_uint_eq(checksum, hello_world_crc);
+
+ // The third file contains 2 v1 members
+ uint8_t *v1_v1 = tuktest_file_from_srcdir("files/good-2-v1-v1.lz",
+ &file_size);
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ strm.avail_in = file_size;
+ strm.next_in = v1_v1;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
+
+ assert_uint_eq(strm.total_in, file_size);
+ checksum = lzma_crc32(output_buffer, strm.total_out, 0);
+ assert_uint_eq(checksum, hello_world_crc);
+
+ lzma_end(&strm);
+}
+
+
+static void
+test_crc(void) {
+ // Test invalid checksum
+ lzma_stream strm = LZMA_STREAM_INIT;
+ size_t file_size;
+ uint8_t *data = tuktest_file_from_srcdir("files/bad-1-v1-crc32.lz",
+ &file_size);
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED), LZMA_OK);
+
+ uint8_t output_buffer[DECODE_CHUNK_SIZE];
+
+ strm.avail_in = file_size;
+ strm.next_in = data;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR);
+
+ // Test ignoring the checksum value - should decode successfully
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED | LZMA_IGNORE_CHECK), LZMA_OK);
+
+ strm.avail_in = file_size;
+ strm.next_in = data;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
+ assert_uint_eq(strm.total_in, file_size);
+
+ // Test tell check
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
+ LZMA_CONCATENATED | LZMA_TELL_ANY_CHECK), LZMA_OK);
+
+ strm.avail_in = file_size;
+ strm.next_in = data;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+ strm.next_out = output_buffer;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_GET_CHECK);
+ assert_uint_eq(lzma_get_check(&strm), LZMA_CHECK_CRC32);
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR);
+ lzma_end(&strm);
+}
+
+
+static void
+test_invalid_magic_bytes(void) {
+ uint8_t lzip_id_string[] = { 0x4C, 0x5A, 0x49, 0x50 };
+ lzma_stream strm = LZMA_STREAM_INIT;
+
+ for (uint32_t i = 0; i < ARRAY_SIZE(lzip_id_string); i++) {
+ // Corrupt magic bytes
+ lzip_id_string[i] ^= 1;
+ uint8_t output_buffer[DECODE_CHUNK_SIZE];
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0),
+ LZMA_OK);
+
+ strm.next_in = lzip_id_string;
+ strm.avail_in = sizeof(lzip_id_string);
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_RUN),
+ LZMA_FORMAT_ERROR);
+
+ // Reset magic bytes
+ lzip_id_string[i] ^= 1;
+ }
+
+ lzma_end(&strm);
+}
+
+
+static void
+test_invalid_version(void)
+{
+ // The file contains a version number that is not 0 or 1,
+ // so it should cause an error
+ decode_expect_error("files/unsupported-1-v234.lz",
+ LZMA_OPTIONS_ERROR);
+}
+
+
+static void
+test_invalid_dictionary_size(void) {
+ // First file has too small dictionary size field
+ decode_expect_error("files/bad-1-v1-dict-1.lz", LZMA_DATA_ERROR);
+
+ // Second file has too large dictionary size field
+ decode_expect_error("files/bad-1-v1-dict-2.lz", LZMA_DATA_ERROR);
+}
+
+
+static void
+test_invalid_uncomp_size(void) {
+ // Test invalid v0 lzip file uncomp size
+ decode_expect_error("files/bad-1-v0-uncomp-size.lz",
+ LZMA_DATA_ERROR);
+
+ // Test invalid v1 lzip file uncomp size
+ decode_expect_error("files/bad-1-v1-uncomp-size.lz",
+ LZMA_DATA_ERROR);
+}
+
+
+static void
+test_invalid_member_size(void) {
+ decode_expect_error("files/bad-1-v1-member-size.lz",
+ LZMA_DATA_ERROR);
+}
+
+
+static void
+test_invalid_memlimit(void) {
+ // A very low memlimit should prevent decoding.
+ // Should be able to update the memlimit after failing
+ size_t file_size;
+ uint8_t *data = tuktest_file_from_srcdir("files/good-1-v1.lz",
+ &file_size);
+
+ uint8_t output_buffer[DECODE_CHUNK_SIZE];
+
+ lzma_stream strm = LZMA_STREAM_INIT;
+
+ assert_lzma_ret(lzma_lzip_decoder(&strm, 1, 0), LZMA_OK);
+
+ strm.next_in = data;
+ strm.avail_in = file_size;
+ strm.next_out = output_buffer;
+ strm.avail_out = DECODE_CHUNK_SIZE;
+
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_MEMLIMIT_ERROR);
+
+ // Up the memlimit so decoding can continue.
+ // First only increase by a small amount and expect an error
+ assert_lzma_ret(lzma_memlimit_set(&strm, 100), LZMA_MEMLIMIT_ERROR);
+ assert_lzma_ret(lzma_memlimit_set(&strm, MEMLIMIT), LZMA_OK);
+
+ // Finish decoding
+ assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
+
+ assert_uint_eq(strm.total_in, file_size);
+ uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0);
+ assert_uint_eq(checksum, hello_world_crc);
+
+ lzma_end(&strm);
+}
+#endif
+
+
+extern int
+main(int argc, char **argv)
+{
+ tuktest_start(argc, argv);
+
+#ifndef HAVE_LZIP_DECODER
+ tuktest_early_skip("lzip decoder disabled");
+#else
+ tuktest_run(test_options);
+ tuktest_run(test_v0_decode);
+ tuktest_run(test_v1_decode);
+ tuktest_run(test_v0_trailing);
+ tuktest_run(test_v1_trailing);
+ tuktest_run(test_concatentated);
+ tuktest_run(test_crc);
+ tuktest_run(test_invalid_magic_bytes);
+ tuktest_run(test_invalid_version);
+ tuktest_run(test_invalid_dictionary_size);
+ tuktest_run(test_invalid_uncomp_size);
+ tuktest_run(test_invalid_member_size);
+ tuktest_run(test_invalid_memlimit);
+ return tuktest_end();
+#endif
+
+}