// SPDX-License-Identifier: 0BSD /////////////////////////////////////////////////////////////////////////////// // /// \file test_block_header.c /// \brief Tests Block Header coders // // Authors: Lasse Collin // Jia Tan // /////////////////////////////////////////////////////////////////////////////// #include "tests.h" static lzma_options_lzma opt_lzma; // Used in test_lzma_block_header_decode() between tests to ensure // no artifacts are leftover in the block struct that could influence // later tests. #define RESET_BLOCK(block, buf) \ do { \ lzma_filter *filters_ = (block).filters; \ lzma_filters_free(filters_, NULL); \ memzero((buf), sizeof((buf))); \ memzero(&(block), sizeof(lzma_block)); \ (block).filters = filters_; \ (block).check = LZMA_CHECK_CRC32; \ } while (0); #ifdef HAVE_ENCODERS static lzma_filter filters_none[1] = { { .id = LZMA_VLI_UNKNOWN, }, }; static lzma_filter filters_one[2] = { { .id = LZMA_FILTER_LZMA2, .options = &opt_lzma, }, { .id = LZMA_VLI_UNKNOWN, } }; // These filters are only used in test_lzma_block_header_decode() // which only runs if encoders and decoders are configured. #ifdef HAVE_DECODERS static lzma_filter filters_four[5] = { { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_LZMA2, .options = &opt_lzma, }, { .id = LZMA_VLI_UNKNOWN, } }; #endif static lzma_filter filters_five[6] = { { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_X86, .options = NULL, }, { .id = LZMA_FILTER_LZMA2, .options = &opt_lzma, }, { .id = LZMA_VLI_UNKNOWN, } }; #endif static void test_lzma_block_header_size(void) { #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_block block = { .version = 0, .filters = filters_one, .compressed_size = LZMA_VLI_UNKNOWN, .uncompressed_size = LZMA_VLI_UNKNOWN, .check = LZMA_CHECK_CRC32 }; // 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); // 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); } 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 = INVALID_LZMA_CHECK_ID; 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); 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); // 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 = INVALID_LZMA_CHECK_ID; 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) + 1U, 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 RESET_BLOCK(decoded_block, out); // 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); RESET_BLOCK(decoded_block, out); // 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); RESET_BLOCK(decoded_block, out); // 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); lzma_filters_free(decoded_filters, NULL); // 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); // Free the filters for the last time since all other cases should // result in an error. lzma_filters_free(decoded_filters, NULL); // Test bad check type decoded_block.check = INVALID_LZMA_CHECK_ID; 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); out[decoded_block.header_size - 1] += 10; // Test non-NULL padding out[decoded_block.header_size - 5] = 1; // 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); // 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 } extern int main(int argc, char **argv) { tuktest_start(argc, argv); if (lzma_lzma_preset(&opt_lzma, 1)) tuktest_error("lzma_lzma_preset() failed"); tuktest_run(test_lzma_block_header_size); tuktest_run(test_lzma_block_header_encode); tuktest_run(test_lzma_block_header_decode); return tuktest_end(); }