From 827ac5b4821491fd3afe0d0e1ddac326253aeb66 Mon Sep 17 00:00:00 2001 From: Jia Tan Date: Thu, 6 Oct 2022 17:00:38 +0800 Subject: [PATCH] Tests: Refactor test_block_header.c. test_block_header now achieves higher test coverage. Also the test will now compile and skip properly if encoders or decoders are disabled. Thanks to Sebastian Andrzej Siewior. --- tests/test_block_header.c | 562 +++++++++++++++++++++++++++----------- 1 file changed, 408 insertions(+), 154 deletions(-) diff --git a/tests/test_block_header.c b/tests/test_block_header.c index 2f0184ac..6b42df22 100644 --- a/tests/test_block_header.c +++ b/tests/test_block_header.c @@ -3,7 +3,8 @@ /// \file test_block_header.c /// \brief Tests Block Header coders // -// Author: Lasse Collin +// Authors: Lasse Collin +// Jia Tan // // This file has been put into the public domain. // You can do whatever you want with this file. @@ -13,12 +14,10 @@ #include "tests.h" -static uint8_t buf[LZMA_BLOCK_HEADER_SIZE_MAX]; -static lzma_block known_options; -static lzma_block decoded_options; - static lzma_options_lzma opt_lzma; + +#ifdef HAVE_ENCODERS static lzma_filter filters_none[1] = { { .id = LZMA_VLI_UNKNOWN, @@ -75,163 +74,423 @@ static lzma_filter filters_five[6] = { .id = LZMA_VLI_UNKNOWN, } }; +#endif static void -code(void) +test_lzma_block_header_size(void) { - assert_lzma_ret(lzma_block_header_encode(&known_options, buf), - LZMA_OK); +#ifndef HAVE_ENCODERS + assert_skip("Encoder support disabled"); +#else + if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86)) + assert_skip("x86 BCJ encoder is disabled"); - lzma_filter filters[LZMA_FILTERS_MAX + 1]; - memcrap(filters, sizeof(filters)); - memcrap(&decoded_options, sizeof(decoded_options)); - - decoded_options.header_size = known_options.header_size; - decoded_options.check = known_options.check; - decoded_options.filters = filters; - assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), - LZMA_OK); - - assert_uint_eq(decoded_options.compressed_size, - known_options.compressed_size); - assert_uint_eq(decoded_options.uncompressed_size, - known_options.uncompressed_size); - - for (size_t i = 0; known_options.filters[i].id - != LZMA_VLI_UNKNOWN; ++i) - assert_uint_eq(filters[i].id, known_options.filters[i].id); - - for (size_t i = 0; i < LZMA_FILTERS_MAX; ++i) - free(decoded_options.filters[i].options); -} - - -static void -test1(void) -{ - known_options = (lzma_block){ - .check = LZMA_CHECK_NONE, - .compressed_size = LZMA_VLI_UNKNOWN, - .uncompressed_size = LZMA_VLI_UNKNOWN, - .filters = NULL, - }; - - assert_lzma_ret(lzma_block_header_size(&known_options), - LZMA_PROG_ERROR); - - known_options.filters = filters_none; - assert_lzma_ret(lzma_block_header_size(&known_options), - LZMA_PROG_ERROR); - - known_options.filters = filters_five; - assert_lzma_ret(lzma_block_header_size(&known_options), - LZMA_PROG_ERROR); - - known_options.filters = filters_one; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - - // Some invalid value, which gets ignored. - known_options.check = (lzma_check)(99); - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - - known_options.compressed_size = 5; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - - known_options.compressed_size = 0; // Cannot be zero. - assert_lzma_ret(lzma_block_header_size(&known_options), - LZMA_PROG_ERROR); - - // LZMA_VLI_MAX is too big to keep the total size of the Block - // a valid VLI, but lzma_block_header_size() is not meant - // to validate it. (lzma_block_header_encode() must validate it.) - known_options.compressed_size = LZMA_VLI_MAX; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - - known_options.compressed_size = LZMA_VLI_UNKNOWN; - known_options.uncompressed_size = 0; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - - known_options.uncompressed_size = LZMA_VLI_MAX + 1; - assert_lzma_ret(lzma_block_header_size(&known_options), - LZMA_PROG_ERROR); -} - - -static void -test2(void) -{ - known_options = (lzma_block){ - .check = LZMA_CHECK_CRC32, - .compressed_size = LZMA_VLI_UNKNOWN, - .uncompressed_size = LZMA_VLI_UNKNOWN, - .filters = filters_four, - }; - - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - code(); - - known_options.compressed_size = 123456; - known_options.uncompressed_size = 234567; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - code(); - - // We can make the sizes smaller while keeping the header size - // the same. - known_options.compressed_size = 12; - known_options.uncompressed_size = 23; - code(); -} - - -static void -test3(void) -{ - known_options = (lzma_block){ - .check = LZMA_CHECK_CRC32, - .compressed_size = LZMA_VLI_UNKNOWN, - .uncompressed_size = LZMA_VLI_UNKNOWN, + lzma_block block = { + .version = 0, .filters = filters_one, + .compressed_size = LZMA_VLI_UNKNOWN, + .uncompressed_size = LZMA_VLI_UNKNOWN, + .check = LZMA_CHECK_CRC32 }; - assert_lzma_ret(lzma_block_header_size(&known_options), LZMA_OK); - known_options.header_size += 4; - assert_lzma_ret(lzma_block_header_encode(&known_options, buf), - LZMA_OK); + // Test that all initial options are valid + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); - lzma_filter filters[LZMA_FILTERS_MAX + 1]; - decoded_options.header_size = known_options.header_size; - decoded_options.check = known_options.check; - decoded_options.filters = filters; + // Test invalid version number + for (uint32_t i = 2; i < 20; i++) { + block.version = i; + assert_lzma_ret(lzma_block_header_size(&block), + LZMA_OPTIONS_ERROR); + } - // Wrong size - ++buf[0]; - assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), + block.version = 1; + + // Test invalid compressed size + block.compressed_size = 0; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + + block.compressed_size = LZMA_VLI_MAX + 1; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + block.compressed_size = LZMA_VLI_UNKNOWN; + + // Test invalid uncompressed size + block.uncompressed_size = LZMA_VLI_MAX + 1; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + block.uncompressed_size = LZMA_VLI_MAX; + + // Test invalid filters + block.filters = NULL; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + + block.filters = filters_none; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + + block.filters = filters_five; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_PROG_ERROR); + + block.filters = filters_one; + + // Test setting compressed_size to something valid + block.compressed_size = 4096; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); + + // Test setting uncompressed_size to something valid + block.uncompressed_size = 4096; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); + + // This should pass, but header_size will be an invalid value + // because the total block size will not be able to fit in a valid + // lzma_vli. This way a temporary value can be used to reserve + // space for the header and later the actual value can be set. + block.compressed_size = LZMA_VLI_MAX; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); + + // Use an invalid value for a filter option. This should still pass + // because the size of the LZMA2 properties is known by liblzma + // without reading any of the options so it doesn't validate them. + lzma_options_lzma bad_ops; + assert_false(lzma_lzma_preset(&bad_ops, 1)); + bad_ops.pb = 0x1000; + + lzma_filter bad_filters[2] = { + { + .id = LZMA_FILTER_LZMA2, + .options = &bad_ops + }, + { + .id = LZMA_VLI_UNKNOWN, + .options = NULL + } + }; + + block.filters = bad_filters; + + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); + + // Use an invalid block option. The check type isn't stored in + // the Block Header and so _header_size ignores it. + block.check = 0x1000; + block.ignore_check = false; + + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_uint(block.header_size, >=, LZMA_BLOCK_HEADER_SIZE_MIN); + assert_uint(block.header_size, <=, LZMA_BLOCK_HEADER_SIZE_MAX); + assert_uint_eq(block.header_size % 4, 0); +#endif +} + + +static void +test_lzma_block_header_encode(void) +{ +#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS) + assert_skip("Encoder or decoder support disabled"); +#else + + if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86) + || !lzma_filter_decoder_is_supported(LZMA_FILTER_X86)) + assert_skip("x86 BCJ encoder and/or decoder " + "is disabled"); + + lzma_block block = { + .version = 1, + .filters = filters_one, + .compressed_size = LZMA_VLI_UNKNOWN, + .uncompressed_size = LZMA_VLI_UNKNOWN, + .check = LZMA_CHECK_CRC32, + }; + + // Ensure all block options are valid before changes are tested + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + + uint8_t out[LZMA_BLOCK_HEADER_SIZE_MAX]; + + // Test invalid block version + for (uint32_t i = 2; i < 20; i++) { + block.version = i; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + } + + block.version = 1; + + // Test invalid header size (< min, > max, % 4 != 0) + block.header_size = LZMA_BLOCK_HEADER_SIZE_MIN - 4; + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_PROG_ERROR); - --buf[0]; + block.header_size = LZMA_BLOCK_HEADER_SIZE_MIN + 2; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + block.header_size = LZMA_BLOCK_HEADER_SIZE_MAX + 4; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); - // Wrong CRC32 - buf[known_options.header_size - 1] ^= 1; - assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), + // Test invalid compressed_size + block.compressed_size = 0; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + block.compressed_size = LZMA_VLI_MAX + 1; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + + // This test passes test_lzma_block_header_size, but should + // fail here because there is not enough space to encode the + // proper block size because the total size is too big to fit + // in an lzma_vli + block.compressed_size = LZMA_VLI_MAX; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + block.compressed_size = LZMA_VLI_UNKNOWN; + + // Test invalid uncompressed size + block.uncompressed_size = LZMA_VLI_MAX + 1; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + block.uncompressed_size = LZMA_VLI_UNKNOWN; + + // Test invalid block check + block.check = 0x1000; + block.ignore_check = false; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + block.check = LZMA_CHECK_CRC32; + + // Test invalid filters + block.filters = NULL; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + + block.filters = filters_none; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + + block.filters = filters_five; + block.header_size = LZMA_BLOCK_HEADER_SIZE_MAX - 4; + assert_lzma_ret(lzma_block_header_encode(&block, out), + LZMA_PROG_ERROR); + + // Test valid encoding and verify bytes of block header. + // More complicated tests for encoding headers are included + // in test_lzma_block_header_decode. + block.filters = filters_one; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK); + + // First read block header size from out and verify + // that it == (encoded size + 1) * 4 + uint32_t header_size = (out[0] + 1U) * 4; + assert_uint_eq(header_size, block.header_size); + + // Next read block flags + uint8_t flags = out[1]; + + // Should have number of filters = 1 + assert_uint_eq((flags & 0x3) + 1, 1); + + // Bits 2-7 must be empty not set + assert_uint_eq(flags & (0xFF - 0x3), 0); + + // Verify filter flags + // Decode Filter ID + lzma_vli filter_id = 0; + size_t pos = 2; + assert_lzma_ret(lzma_vli_decode(&filter_id, NULL, out, + &pos, header_size), LZMA_OK); + assert_uint_eq(filter_id, filters_one[0].id); + + // Decode Size of Properties + lzma_vli prop_size = 0; + assert_lzma_ret(lzma_vli_decode(&prop_size, NULL, out, + &pos, header_size), LZMA_OK); + + // LZMA2 has 1 byte prop size + assert_uint_eq(prop_size, 1); + uint8_t expected_filter_props = 0; + assert_lzma_ret(lzma_properties_encode(filters_one, + &expected_filter_props), LZMA_OK); + assert_uint_eq(out[pos], expected_filter_props); + pos++; + + // Check null-padding + for (size_t i = pos; i < header_size - 4; i++) + assert_uint_eq(out[i], 0); + + // Check CRC32 + assert_uint_eq(read32le(&out[header_size - 4]), lzma_crc32(out, + header_size - 4, 0)); +#endif +} + + +#if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS) +// Helper function to compare two lzma_block structures field by field +static void +compare_blocks(lzma_block *block_expected, lzma_block *block_actual) +{ + assert_uint_eq(block_actual->version, block_expected->version); + assert_uint_eq(block_actual->compressed_size, + block_expected->compressed_size); + assert_uint_eq(block_actual->uncompressed_size, + block_expected->uncompressed_size); + assert_uint_eq(block_actual->check, block_expected->check); + assert_uint_eq(block_actual->header_size, block_expected->header_size); + + // Compare filter IDs + assert_true(block_expected->filters && block_actual->filters); + lzma_filter expected_filter = block_expected->filters[0]; + uint32_t filter_count = 0; + while (expected_filter.id != LZMA_VLI_UNKNOWN) { + assert_uint_eq(block_actual->filters[filter_count].id, + expected_filter.id); + expected_filter = block_expected->filters[++filter_count]; + } + + assert_uint_eq(block_actual->filters[filter_count].id, + LZMA_VLI_UNKNOWN); +} +#endif + + +static void +test_lzma_block_header_decode(void) +{ +#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS) + assert_skip("Encoder or decoder support disabled"); +#else + if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86) + || !lzma_filter_decoder_is_supported(LZMA_FILTER_X86)) + assert_skip("x86 BCJ encoder and/or decoder " + "is disabled"); + + lzma_block block = { + .filters = filters_one, + .compressed_size = LZMA_VLI_UNKNOWN, + .uncompressed_size = LZMA_VLI_UNKNOWN, + .check = LZMA_CHECK_CRC32, + .version = 0 + }; + + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + + // Encode block header with simple options + uint8_t out[LZMA_BLOCK_HEADER_SIZE_MAX]; + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK); + + // Decode block header and check that the options match + lzma_filter decoded_filters[LZMA_FILTERS_MAX + 1]; + lzma_block decoded_block = { + .version = 0, + .filters = decoded_filters, + .check = LZMA_CHECK_CRC32 + }; + decoded_block.header_size = lzma_block_header_size_decode(out[0]); + + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OK); + compare_blocks(&block, &decoded_block); + + // Reset output buffer and decoded_block + memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX); + memzero(&decoded_block, sizeof(lzma_block)); + decoded_block.filters = decoded_filters; + decoded_block.check = LZMA_CHECK_CRC32; + + // Test with compressed size set + block.compressed_size = 4096; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK); + decoded_block.header_size = lzma_block_header_size_decode(out[0]); + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OK); + compare_blocks(&block, &decoded_block); + + memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX); + memzero(&decoded_block, sizeof(lzma_block)); + decoded_block.filters = decoded_filters; + decoded_block.check = LZMA_CHECK_CRC32; + + // Test with uncompressed size set + block.uncompressed_size = 4096; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK); + decoded_block.header_size = lzma_block_header_size_decode(out[0]); + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OK); + compare_blocks(&block, &decoded_block); + + memzero(out, LZMA_BLOCK_HEADER_SIZE_MAX); + memzero(&decoded_block, sizeof(lzma_block)); + decoded_block.filters = decoded_filters; + decoded_block.check = LZMA_CHECK_CRC32; + + // Test with multiple filters + block.filters = filters_four; + assert_lzma_ret(lzma_block_header_size(&block), LZMA_OK); + assert_lzma_ret(lzma_block_header_encode(&block, out), LZMA_OK); + decoded_block.header_size = lzma_block_header_size_decode(out[0]); + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OK); + compare_blocks(&block, &decoded_block); + + memzero(&decoded_block, sizeof(lzma_block)); + decoded_block.filters = decoded_filters; + decoded_block.check = LZMA_CHECK_CRC32; + decoded_block.header_size = lzma_block_header_size_decode(out[0]); + + // Test with too high version. The decoder will set it to a version + // that it supports. + decoded_block.version = 2; + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OK); + assert_uint_eq(decoded_block.version, 1); + + // Test bad check type + decoded_block.check = LZMA_CHECK_ID_MAX + 1; + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_PROG_ERROR); + decoded_block.check = LZMA_CHECK_CRC32; + + // Test bad check value + out[decoded_block.header_size - 1] -= 10; + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), LZMA_DATA_ERROR); - buf[known_options.header_size - 1] ^= 1; + out[decoded_block.header_size - 1] += 10; - // Unsupported filter - // NOTE: This may need updating when new IDs become supported. - buf[2] ^= 0x1F; - write32le(buf + known_options.header_size - 4, - lzma_crc32(buf, known_options.header_size - 4, 0)); - assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), - LZMA_OPTIONS_ERROR); - buf[2] ^= 0x1F; + // Test non-NULL padding + out[decoded_block.header_size - 5] = 1; - // Non-nul Padding - buf[known_options.header_size - 4 - 1] ^= 1; - write32le(buf + known_options.header_size - 4, - lzma_crc32(buf, known_options.header_size - 4, 0)); - assert_lzma_ret(lzma_block_header_decode(&decoded_options, NULL, buf), + // Recompute CRC32 + write32le(&out[decoded_block.header_size - 4], lzma_crc32(out, + decoded_block.header_size - 4, 0)); + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), LZMA_OPTIONS_ERROR); - buf[known_options.header_size - 4 - 1] ^= 1; + + // Test unsupported flags + out[1] = 0xFF; + + // Recompute CRC32 + write32le(&out[decoded_block.header_size - 4], lzma_crc32(out, + decoded_block.header_size - 4, 0)); + assert_lzma_ret(lzma_block_header_decode(&decoded_block, NULL, out), + LZMA_OPTIONS_ERROR); +#endif } @@ -240,17 +499,12 @@ main(int argc, char **argv) { tuktest_start(argc, argv); - if (!lzma_filter_encoder_is_supported(LZMA_FILTER_X86) - || !lzma_filter_decoder_is_supported(LZMA_FILTER_X86)) - tuktest_early_skip("x86 BCJ encoder and/or decoder " - "is disabled"); - if (lzma_lzma_preset(&opt_lzma, 1)) tuktest_error("lzma_lzma_preset() failed"); - tuktest_run(test1); - tuktest_run(test2); - tuktest_run(test3); + tuktest_run(test_lzma_block_header_size); + tuktest_run(test_lzma_block_header_encode); + tuktest_run(test_lzma_block_header_decode); return tuktest_end(); }