mirror of
https://git.tukaani.org/xz.git
synced 2024-04-04 12:36:23 +02:00
520 lines
15 KiB
C
520 lines
15 KiB
C
// 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();
|
|
}
|