// SPDX-License-Identifier: 0BSD

///////////////////////////////////////////////////////////////////////////////
//
/// \file       test_filter_flags.c
/// \brief      Tests Filter Flags coders
//
//  Authors:    Jia Tan
//              Lasse Collin
//
///////////////////////////////////////////////////////////////////////////////

#include "tests.h"

// FIXME: This is from src/liblzma/common/common.h but it cannot be
// included here. This constant is needed in only a few files, perhaps
// move it to some other internal header or create a new one?
#define LZMA_FILTER_RESERVED_START (LZMA_VLI_C(1) << 62)


#if defined(HAVE_ENCODERS)
// No tests are run without encoders, so init the global filters
// only when the encoders are enabled.
static lzma_filter lzma1_filter = { LZMA_FILTER_LZMA1, NULL };
static lzma_filter lzma2_filter = { LZMA_FILTER_LZMA2, NULL };
static lzma_filter delta_filter = { LZMA_FILTER_DELTA, NULL };

static lzma_filter bcj_filters_encoders[] = {
#ifdef HAVE_ENCODER_X86
	{ LZMA_FILTER_X86, NULL },
#endif
#ifdef HAVE_ENCODER_POWERPC
	{ LZMA_FILTER_POWERPC, NULL },
#endif
#ifdef HAVE_ENCODER_IA64
	{ LZMA_FILTER_IA64, NULL },
#endif
#ifdef HAVE_ENCODER_ARM
	{ LZMA_FILTER_ARM, NULL },
#endif
#ifdef HAVE_ENCODER_ARM64
	{ LZMA_FILTER_ARM64, NULL },
#endif
#ifdef HAVE_ENCODER_ARMTHUMB
	{ LZMA_FILTER_ARMTHUMB, NULL },
#endif
#ifdef HAVE_ENCODER_SPARC
	{ LZMA_FILTER_SPARC, NULL },
#endif
#ifdef HAVE_ENCODER_RISCV
	{ LZMA_FILTER_RISCV, NULL },
#endif
	{ LZMA_VLI_UNKNOWN, NULL }
};

// HAVE_ENCODERS ifdef not terminated here because decoders are
// only used if encoders are, but encoders can still be used
// even if decoders are not.

#ifdef HAVE_DECODERS
static lzma_filter bcj_filters_decoders[] = {
#ifdef HAVE_DECODER_X86
	{ LZMA_FILTER_X86, NULL },
#endif
#ifdef HAVE_DECODER_POWERPC
	{ LZMA_FILTER_POWERPC, NULL },
#endif
#ifdef HAVE_DECODER_IA64
	{ LZMA_FILTER_IA64, NULL },
#endif
#ifdef HAVE_DECODER_ARM
	{ LZMA_FILTER_ARM, NULL },
#endif
#ifdef HAVE_DECODER_ARM64
	{ LZMA_FILTER_ARM64, NULL },
#endif
#ifdef HAVE_DECODER_ARMTHUMB
	{ LZMA_FILTER_ARMTHUMB, NULL },
#endif
#ifdef HAVE_DECODER_SPARC
	{ LZMA_FILTER_SPARC, NULL },
#endif
#ifdef HAVE_DECODER_RISCV
	{ LZMA_FILTER_RISCV, NULL },
#endif
	{ LZMA_VLI_UNKNOWN, NULL }
};
#endif
#endif


static void
test_lzma_filter_flags_size(void)
{
#ifndef HAVE_ENCODERS
	assert_skip("Encoder support disabled");
#else
	// For each supported filter, test that the size can be calculated
	// and that the size calculated is reasonable. A reasonable size
	// must be greater than 0, but less than the maximum size for the
	// block header.
	uint32_t size = 0;
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA1)) {
		// LZMA1 isn't supported in .xz so we get LZMA_PROG_ERROR.
		assert_lzma_ret(lzma_filter_flags_size(&size,
				&lzma1_filter), LZMA_PROG_ERROR);
	}

	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2)) {
		assert_lzma_ret(lzma_filter_flags_size(&size,
				&lzma2_filter), LZMA_OK);
		assert_true(size != 0 && size < LZMA_BLOCK_HEADER_SIZE_MAX);
	}

	for (size_t i = 0; bcj_filters_encoders[i].id != LZMA_VLI_UNKNOWN;
			++i) {
		assert_lzma_ret(lzma_filter_flags_size(&size,
				&bcj_filters_encoders[i]), LZMA_OK);
		assert_true(size != 0 && size < LZMA_BLOCK_HEADER_SIZE_MAX);
	}

	if (lzma_filter_encoder_is_supported(LZMA_FILTER_DELTA)) {
		assert_lzma_ret(lzma_filter_flags_size(&size,
				&delta_filter), LZMA_OK);
		assert_true(size != 0 && size < LZMA_BLOCK_HEADER_SIZE_MAX);
	}

	// Test invalid Filter IDs
	lzma_filter bad_filter = { 2, NULL };

	assert_lzma_ret(lzma_filter_flags_size(&size, &bad_filter),
			LZMA_OPTIONS_ERROR);
	bad_filter.id = LZMA_VLI_MAX;
	assert_lzma_ret(lzma_filter_flags_size(&size, &bad_filter),
			LZMA_PROG_ERROR);
	bad_filter.id = LZMA_FILTER_RESERVED_START;
	assert_lzma_ret(lzma_filter_flags_size(&size, &bad_filter),
			LZMA_PROG_ERROR);
#endif
}


// Helper function for test_lzma_filter_flags_encode.
// The should_encode parameter represents if the encoding operation
// is expected to fail.
// Avoid data -> encode -> decode -> compare to data.
// Instead create expected encoding and compare to result from
// lzma_filter_flags_encode.
// Filter Flags in .xz are encoded as:
// |Filter ID (VLI)|Size of Properties (VLI)|Filter Properties|
#if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS)
static void
verify_filter_flags_encode(lzma_filter *filter, bool should_encode)
{
	uint32_t size = 0;

	// First calculate the size of Filter Flags to know how much
	// memory to allocate to hold the encoded Filter Flags
	assert_lzma_ret(lzma_filter_flags_size(&size, filter), LZMA_OK);
	uint8_t *encoded_out = tuktest_malloc(size);
	size_t out_pos = 0;
	if (!should_encode) {
		assert_false(lzma_filter_flags_encode(filter, encoded_out,
				&out_pos, size) == LZMA_OK);
		return;
	}

	// Next encode the Filter Flags for the provided filter
	assert_lzma_ret(lzma_filter_flags_encode(filter, encoded_out,
			&out_pos, size), LZMA_OK);
	assert_uint_eq(size, out_pos);

	// Next decode the VLI for the Filter ID and verify it matches
	// the expected Filter ID
	size_t filter_id_vli_size = 0;
	lzma_vli filter_id = 0;
	assert_lzma_ret(lzma_vli_decode(&filter_id, NULL, encoded_out,
			&filter_id_vli_size, size), LZMA_OK);
	assert_uint_eq(filter->id, filter_id);

	// Next decode the Size of Properties and ensure it equals
	// the expected size.
	// Expected size should be:
	// total filter flag length - size of filter id VLI + size of
	//                            property size VLI
	// Not verifying the contents of Filter Properties since
	// that belongs in a different test
	size_t size_of_properties_vli_size = 0;
	lzma_vli size_of_properties = 0;
	assert_lzma_ret(lzma_vli_decode(&size_of_properties, NULL,
			encoded_out + filter_id_vli_size,
			&size_of_properties_vli_size, size), LZMA_OK);
	assert_uint_eq(size - (size_of_properties_vli_size +
			filter_id_vli_size), size_of_properties);
}
#endif


static void
test_lzma_filter_flags_encode(void)
{
#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS)
	assert_skip("Encoder or decoder support disabled");
#else
	// No test for LZMA1 since the .xz format does not support LZMA1
	// and so the flags cannot be encoded for that filter
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2)) {
		// Test with NULL options that should fail
		lzma_options_lzma *options = lzma2_filter.options;
		lzma2_filter.options = NULL;
		verify_filter_flags_encode(&lzma2_filter, false);

		// Place options back in the filter, and test should pass
		lzma2_filter.options = options;
		verify_filter_flags_encode(&lzma2_filter, true);
	}

	// NOTE: Many BCJ filters require that start_offset is a multiple
	// of some power of two. The Filter Flags encoder and decoder don't
	// completely validate the options and thus 257 passes the tests
	// with all BCJ filters. It would be caught when initializing
	// a filter chain encoder or decoder.
	lzma_options_bcj bcj_options = {
		.start_offset = 257
	};

	for (size_t i = 0; bcj_filters_encoders[i].id != LZMA_VLI_UNKNOWN;
			++i) {
		// NULL options should pass for bcj filters
		verify_filter_flags_encode(&bcj_filters_encoders[i], true);
		lzma_filter bcj_with_options = {
				bcj_filters_encoders[i].id, &bcj_options };
		verify_filter_flags_encode(&bcj_with_options, true);
	}

	if (lzma_filter_encoder_is_supported(LZMA_FILTER_DELTA)) {
		lzma_options_delta delta_opts_below_min = {
			.type = LZMA_DELTA_TYPE_BYTE,
			.dist = LZMA_DELTA_DIST_MIN - 1
		};

		lzma_options_delta delta_opts_above_max = {
			.type = LZMA_DELTA_TYPE_BYTE,
			.dist = LZMA_DELTA_DIST_MAX + 1
		};

		verify_filter_flags_encode(&delta_filter, true);

		lzma_filter delta_filter_bad_options = {
				LZMA_FILTER_DELTA, &delta_opts_below_min };

		// Next test error case using minimum - 1 delta distance
		verify_filter_flags_encode(&delta_filter_bad_options, false);

		// Next test error case using maximum + 1 delta distance
		delta_filter_bad_options.options = &delta_opts_above_max;
		verify_filter_flags_encode(&delta_filter_bad_options, false);

		// Next test NULL case
		delta_filter_bad_options.options = NULL;
		verify_filter_flags_encode(&delta_filter_bad_options, false);
	}

	// Test expected failing cases
	lzma_filter bad_filter = { LZMA_FILTER_RESERVED_START, NULL };
	size_t out_pos = 0;
	size_t out_size = LZMA_BLOCK_HEADER_SIZE_MAX;
	uint8_t out[LZMA_BLOCK_HEADER_SIZE_MAX];

	// Filter ID outside of valid range
	assert_lzma_ret(lzma_filter_flags_encode(&bad_filter, out, &out_pos,
			out_size), LZMA_PROG_ERROR);
	out_pos = 0;
	bad_filter.id = LZMA_VLI_MAX + 1;
	assert_lzma_ret(lzma_filter_flags_encode(&bad_filter, out, &out_pos,
			out_size), LZMA_PROG_ERROR);
	out_pos = 0;

	// Invalid Filter ID
	bad_filter.id = 2;
	assert_lzma_ret(lzma_filter_flags_encode(&bad_filter, out, &out_pos,
			out_size), LZMA_OPTIONS_ERROR);
	out_pos = 0;

	// Out size too small
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2)) {
		uint32_t bad_size = 0;

		// First test with 0 output size
		assert_lzma_ret(lzma_filter_flags_encode(
				&lzma2_filter, out, &out_pos, 0),
				LZMA_PROG_ERROR);

		// Next calculate the size needed to encode and
		// use less than that
		assert_lzma_ret(lzma_filter_flags_size(&bad_size,
				&lzma2_filter), LZMA_OK);

		assert_lzma_ret(lzma_filter_flags_encode(
				&lzma2_filter, out, &out_pos,
				bad_size - 1), LZMA_PROG_ERROR);
		out_pos = 0;
	}

	// Invalid options
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_DELTA)) {
		bad_filter.id = LZMA_FILTER_DELTA;

		// First test with NULL options
		assert_lzma_ret(lzma_filter_flags_encode(&bad_filter, out,
				&out_pos, out_size), LZMA_PROG_ERROR);
		out_pos = 0;

		// Next test with invalid options
		lzma_options_delta bad_options = {
			.dist = LZMA_DELTA_DIST_MAX + 1,
			.type = LZMA_DELTA_TYPE_BYTE
		};
		bad_filter.options = &bad_options;

		assert_lzma_ret(lzma_filter_flags_encode(&bad_filter, out,
				&out_pos, out_size), LZMA_PROG_ERROR);
	}
#endif
}


// Helper function for test_lzma_filter_flags_decode.
// Encodes the filter_in without using lzma_filter_flags_encode.
// Leaves the specific assertions of filter_out options to the caller
// because it is agnostic to the type of options used in the call
#if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS)
static void
verify_filter_flags_decode(const lzma_filter *filter_in,
		lzma_filter *filter_out)
{
	uint32_t total_size = 0;

	assert_lzma_ret(lzma_filter_flags_size(&total_size, filter_in),
			LZMA_OK);
	assert_uint(total_size, >, 0);
	uint8_t *filter_flag_buffer = tuktest_malloc(total_size);

	uint32_t properties_size = 0;
	size_t out_pos = 0;
	size_t in_pos = 0;
	assert_lzma_ret(lzma_properties_size(&properties_size, filter_in),
			LZMA_OK);
	assert_lzma_ret(lzma_vli_encode(filter_in->id, NULL,
			filter_flag_buffer, &out_pos, total_size), LZMA_OK);
	assert_lzma_ret(lzma_vli_encode(properties_size, NULL,
			filter_flag_buffer, &out_pos, total_size),
			LZMA_OK);
	assert_lzma_ret(lzma_properties_encode(filter_in,
			filter_flag_buffer + out_pos), LZMA_OK);
	assert_lzma_ret(lzma_filter_flags_decode(filter_out, NULL,
			filter_flag_buffer, &in_pos, total_size),
			LZMA_OK);
	assert_uint_eq(filter_in->id, filter_out->id);
}
#endif


static void
test_lzma_filter_flags_decode(void)
{
#if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS)
	assert_skip("Encoder or decoder support disabled");
#else
	// For each filter, only run the decoder test if both the encoder
	// and decoder are enabled. This is because verify_filter_flags_decode
	// uses lzma_filter_flags_size which requires the encoder.
	if (lzma_filter_decoder_is_supported(LZMA_FILTER_LZMA2) &&
			lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2)) {
		lzma_filter lzma2_decoded = { LZMA_FILTER_LZMA2, NULL };

		verify_filter_flags_decode(&lzma2_filter, &lzma2_decoded);

		lzma_options_lzma *expected = lzma2_filter.options;
		lzma_options_lzma *decoded = lzma2_decoded.options;

		// Only the dictionary size is encoded and decoded
		// so only compare those
		assert_uint_eq(decoded->dict_size, expected->dict_size);

		// The decoded options must be freed by the caller
		free(decoded);
	}

	for (size_t i = 0; bcj_filters_decoders[i].id != LZMA_VLI_UNKNOWN;
			++i) {
		if (lzma_filter_encoder_is_supported(
				bcj_filters_decoders[i].id)) {
			lzma_filter bcj_decoded = {
					bcj_filters_decoders[i].id, NULL };

			lzma_filter bcj_encoded = {
					bcj_filters_decoders[i].id, NULL };

			// First test without options
			verify_filter_flags_decode(&bcj_encoded,
					&bcj_decoded);
			assert_true(bcj_decoded.options == NULL);

			// Next test with start_offset.
			//
			// NOTE: The encoder and decoder don't verify if
			// the start_offset is valid for the filter. Only
			// the encoder or decoder initialization does.
			lzma_options_bcj options = {
				.start_offset = 257
			};

			bcj_encoded.options = &options;
			verify_filter_flags_decode(&bcj_encoded,
					&bcj_decoded);
			lzma_options_bcj *decoded_opts = bcj_decoded.options;
			assert_uint_eq(decoded_opts->start_offset,
					options.start_offset);
			free(decoded_opts);
		}
	}

	if (lzma_filter_decoder_is_supported(LZMA_FILTER_DELTA) &&
			lzma_filter_encoder_is_supported(LZMA_FILTER_DELTA)) {
		lzma_filter delta_decoded = { LZMA_FILTER_DELTA, NULL };

		verify_filter_flags_decode(&delta_filter, &delta_decoded);
		lzma_options_delta *expected = delta_filter.options;
		lzma_options_delta *decoded = delta_decoded.options;
		assert_uint_eq(expected->dist, decoded->dist);
		assert_uint_eq(expected->type, decoded->type);

		free(decoded);
	}

	// Test expected failing cases
	uint8_t bad_encoded_filter[LZMA_BLOCK_HEADER_SIZE_MAX];
	lzma_filter bad_filter;

	// Filter ID outside of valid range
	lzma_vli bad_filter_id = LZMA_FILTER_RESERVED_START;
	size_t bad_encoded_out_pos = 0;
	size_t in_pos = 0;

	assert_lzma_ret(lzma_vli_encode(bad_filter_id, NULL,
			bad_encoded_filter, &bad_encoded_out_pos,
			LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_OK);

	assert_lzma_ret(lzma_filter_flags_decode(&bad_filter, NULL,
			bad_encoded_filter, &in_pos,
			LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_DATA_ERROR);

	bad_encoded_out_pos = 0;
	in_pos = 0;

	// Invalid Filter ID
	bad_filter_id = 2;
	bad_encoded_out_pos = 0;
	in_pos = 0;

	assert_lzma_ret(lzma_vli_encode(bad_filter_id, NULL,
			bad_encoded_filter, &bad_encoded_out_pos,
			LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_OK);

	// Next encode Size of Properties with the value of 0
	assert_lzma_ret(lzma_vli_encode(0, NULL,
			bad_encoded_filter, &bad_encoded_out_pos,
			LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_OK);

	// Decode should fail on bad Filter ID
	assert_lzma_ret(lzma_filter_flags_decode(&bad_filter, NULL,
			bad_encoded_filter, &in_pos,
			LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_OPTIONS_ERROR);
	bad_encoded_out_pos = 0;
	in_pos = 0;

	// Outsize too small
	// Encode the LZMA2 filter normally, but then set
	// the out size when decoding as too small
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2) &&
			lzma_filter_decoder_is_supported(LZMA_FILTER_LZMA2)) {
		uint32_t filter_flag_size = 0;
		assert_lzma_ret(lzma_filter_flags_size(&filter_flag_size,
				&lzma2_filter), LZMA_OK);

		assert_lzma_ret(lzma_filter_flags_encode(&lzma2_filter,
				bad_encoded_filter, &bad_encoded_out_pos,
				LZMA_BLOCK_HEADER_SIZE_MAX), LZMA_OK);

		assert_lzma_ret(lzma_filter_flags_decode(&bad_filter, NULL,
				bad_encoded_filter, &in_pos,
				filter_flag_size - 1), LZMA_DATA_ERROR);
	}
#endif
}


extern int
main(int argc, char **argv)
{
	tuktest_start(argc, argv);

#ifdef HAVE_ENCODERS
	// Only init filter options if encoder is supported because decoder
	// tests requires encoder support, so the decoder tests will only
	// run if for a given filter both the encoder and decoder are enabled.
	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA1)) {
		lzma_options_lzma *options = tuktest_malloc(
				sizeof(lzma_options_lzma));
		lzma_lzma_preset(options, LZMA_PRESET_DEFAULT);
		lzma1_filter.options = options;
	}

	if (lzma_filter_encoder_is_supported(LZMA_FILTER_LZMA2)) {
		lzma_options_lzma *options = tuktest_malloc(
				sizeof(lzma_options_lzma));
		lzma_lzma_preset(options, LZMA_PRESET_DEFAULT);
		lzma2_filter.options = options;
	}

	if (lzma_filter_encoder_is_supported(LZMA_FILTER_DELTA)) {
		lzma_options_delta *options = tuktest_malloc(
				sizeof(lzma_options_delta));
		options->dist = LZMA_DELTA_DIST_MIN;
		options->type = LZMA_DELTA_TYPE_BYTE;
		delta_filter.options = options;
	}
#endif

	tuktest_run(test_lzma_filter_flags_size);
	tuktest_run(test_lzma_filter_flags_encode);
	tuktest_run(test_lzma_filter_flags_decode);
	return tuktest_end();
}
