// SPDX-FileCopyrightText: 2021 Dhruv Maroo <dhruvmaru007@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_util.h>
#include "minunit.h"

struct {
	char inflated[1285];
	char deflated[1285];
	int deflated_length;
} test_cases[] = {
	{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n", "\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\xe0\x01\xbd\x01\x3e\x5d\x00\x26\x1b\xca\x46\x67\x5a\xf2\x77\xb8\x7d\x86\xd8\x41\xdb\x05\x35\xcd\x83\xa5\x7c\x12\xa5\x05\xdb\x90\xbd\x2f\x14\xd3\x71\x72\x96\xa8\x8a\x7d\x84\x56\x71\x8d\x6a\x22\x98\xab\x9e\x3d\xc3\x55\xef\xcc\xa5\xc3\xdd\x5b\x8e\xbf\x03\x81\x21\x40\xd6\x26\x91\x02\x45\x4f\x92\xa1\x78\xbb\x8a\x00\xaf\x90\x2a\x26\x92\x02\x23\xe5\x5c\xb3\x2d\xe3\xe8\x5c\x2c\xfb\x32\x21\xc6\x6f\x6a\x37\xb1\x66\x20\xcd\xb7\x52\x7d\x66\xa4\x21\x08\xd1\x44\x14\x6c\x7d\x34\x90\x6d\xd6\x47\xad\x5d\x5a\x90\x76\x28\xc8\xe7\x8f\x78\x22\x47\x07\x17\x9e\x9d\x95\x7f\x6f\x30\xa4\xe0\x3a\x53\xb7\x14\xb6\x42\x9d\x20\xc2\xfd\x88\xb4\x49\xb1\xb6\xf7\xdb\x8c\x7f\xe2\x9d\x58\x9f\x66\x55\x01\x44\x9e\x4c\x21\x6c\x4d\x46\x3c\x16\x9f\xf5\x53\xaa\x19\xe2\xd6\x4b\x56\xc2\x19\xd0\xc1\x3c\x5b\x8b\x1a\x26\xe8\xb8\x41\xa5\xb8\x25\x75\x94\x0c\xe5\x98\xc9\xe3\xdd\x82\xbf\x46\x45\x72\x04\xa2\x88\xf1\xa9\x07\xd6\xe2\xd6\xa4\x5f\xcc\xd5\x91\xa4\x64\xac\xb1\x11\xae\xbd\x20\x5f\x89\x5a\x04\x36\x98\x79\x54\xdd\x29\x26\x5e\x70\x1d\xf4\x87\xc3\x3d\xe6\x4a\x8b\x37\x87\x26\x1f\x6c\x1b\x10\x7c\x43\x72\xff\xef\x57\x3e\xa2\x38\xca\xca\x51\xc1\x12\x8b\xd2\x69\x6f\xf7\x09\xad\xe2\x7a\x3f\x40\xc7\x3a\xe9\xbd\xf2\x25\x5e\x39\xcf\xcf\x10\x30\xb0\x4f\x7a\x72\x07\x21\xed\x25\xdb\x5a\xec\x0d\x0a\x51\x74\x6b\xbc\x25\x0a\x92\x01\x6e\x00\x00\x00\x00\xbc\xfd\xfc\x00\x58\xe0\x91\xd4\x00\x01\xda\x02\xbe\x03\x00\x00\x04\x3f\xdb\xf5\xb1\xc4\x67\xfb\x02\x00\x00\x00\x00\x04\x59\x5a", 384 },
	{ "I’d just like to interject for a moment. What you’re refering to as Linux, is in fact, GNU/Linux, or as I’ve recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.\n\nMany computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called Linux, and many of its users are not aware that it is basically the GNU system, developed by the GNU Project.\n\nThere really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine’s resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called Linux distributions are really distributions of GNU/Linux!\n", "\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\xe0\x05\x01\x02\x96\x5d\x00\x24\xb8\x8c\x09\x93\x1f\xbc\xbd\xc0\xde\x12\xb1\x3a\xc0\x03\x9b\x4b\x35\xf1\x63\x79\xd1\xfc\x63\x08\x76\xe3\x93\x33\xc2\xb1\xa6\xa8\xfd\xe0\x0d\x1f\xf5\x4e\x9e\x1c\x88\x8b\x29\x74\x99\x38\x47\xdf\xa3\x82\x85\x0c\x0d\x2b\xd9\x50\x8f\x1c\x4e\x04\x5f\xe9\xa6\x8c\x1f\x42\xa3\x5f\xd0\x8c\xd4\x02\xa5\xa0\xde\x7d\x92\x68\x60\x16\xa9\x96\x5e\xbb\x23\xf3\x38\x32\xaf\x25\x76\x74\x13\x64\x47\xfd\x1e\x0f\x7b\xe1\x18\xfb\x57\x23\x5b\x83\x13\x8d\xba\xa3\x74\x84\x4a\x61\x1e\xaf\x86\x1f\x68\xe3\x71\xe6\xcc\x29\x75\xb7\xfe\x8c\xe3\x7a\xda\xad\x8d\x58\xe9\x95\x43\x2e\x31\x7b\xe4\xeb\x47\x4d\x00\x1b\xd9\x6c\xb5\x8a\xb8\xa3\x55\x4e\x32\xca\x84\xa9\x87\x87\x24\x1c\x86\x2c\xf7\x60\x8e\xf8\x4b\x79\x14\xdc\x6a\x6a\x0b\xf4\x15\xca\x20\x25\xfc\x59\xbf\x0a\x2b\x2a\x52\xf7\x9c\xef\x00\x57\x73\xdc\x50\x39\xf8\x7d\xa9\x37\x13\x00\xc3\x9f\x78\x29\x9f\x29\x1a\xf0\x43\x7c\xce\xf9\x63\x93\xee\xed\x16\x8c\xa9\x78\x51\x6d\xc2\x59\x48\x18\x76\xc9\x31\xa7\x92\xb5\x91\x53\xce\xcf\xa1\x6d\xd8\xe3\x31\x12\x5b\x5e\x5c\xf6\x51\x83\x40\x5d\x1d\x6a\x09\xc2\x85\x0f\x65\xef\x14\x62\x8b\xdf\xf4\xe5\x5b\xd8\x54\x3e\x75\xc4\x74\x1e\x96\x9f\x94\x10\xfd\x35\x00\x29\x00\xa8\x50\x7a\x15\x0f\x3a\x9e\x46\x67\x07\xac\x85\xe8\x43\xf2\xc1\xc3\x06\x5e\xae\x95\x04\x56\xae\x0e\xe0\x94\x42\xa5\xba\x43\xc8\xc2\xc1\xe4\xb3\xd4\xac\xd6\x0d\xc4\x89\xd9\x44\xa9\xfb\xda\x70\x84\x59\x1f\x5c\x01\xe8\xb5\x40\x11\x65\x74\xb5\xbf\x96\x83\x1d\x44\x92\x2c\x92\x26\x32\x88\x0e\x1a\xc4\xda\x1d\x57\x99\x48\xf8\x10\x1d\xb4\x6f\xad\xce\xc3\xd0\x5d\x9b\xe8\x38\xd4\xec\x5d\x58\xc4\x12\x44\x49\x0e\xaf\x42\x79\xf6\xed\x69\xf8\x4b\x34\x32\xc9\xbf\xba\x56\x09\xa5\x9f\x89\x10\x31\xcf\xc1\x79\x53\x89\xd9\xf0\xeb\xac\x1f\xd5\x84\x44\xcb\x38\x0d\x77\x3e\x16\x04\x49\x5d\x3c\x7d\x14\xaa\xc0\x1a\xeb\xa0\x58\x33\x18\x7c\xc5\xc9\x88\x6f\x8a\x0e\x38\xa7\xbe\x71\x6a\xf1\x35\xbf\x8b\x26\x4d\x72\x10\xb6\xd6\x4f\x75\x55\xed\xcd\x2c\xb8\x72\xc3\xab\x4d\x53\x0e\x6e\x2a\x99\x29\xf5\x6d\xd9\x01\x56\xc0\x19\xe5\x77\x77\x69\x8a\xf6\x80\x89\x5a\x12\xd5\x41\xfe\x71\xfc\x7a\xc9\x94\xa7\xff\xf6\x07\xb8\x46\xcc\x07\x1a\xc1\x7b\xdf\xd2\x2f\x8e\x12\xbf\x3a\xb8\x9a\x0a\x0a\xe1\xb1\xd6\x96\xc6\x9d\x42\x1a\xc5\x06\x2e\x4a\x3b\x11\x19\x97\x89\x95\x36\x68\xf9\x25\x1f\x9a\x6e\xd6\xdf\x23\x25\x81\xef\x1f\x36\x20\x01\x0f\x2f\x67\x52\x6c\x26\x9a\x6d\xdb\x4d\xe9\xe5\xb6\xde\x0e\x14\x3f\xff\x62\x50\x5e\x17\xea\xa0\x4d\xd0\x6b\xe8\x87\xb2\x04\xf3\x43\x24\xdc\x1b\x62\x6b\x1e\x87\x2c\xbb\x69\x11\xcc\xd8\xe5\xd9\x78\xda\x79\x14\x0f\x8b\x1b\xbc\x52\xac\x94\x55\xa2\x81\x88\xcb\x90\xea\x51\x5d\x42\x91\xe9\x1f\xce\xb8\x9f\xcd\x12\x75\xeb\x50\x2c\x5c\xb6\xd6\x32\x04\xe1\x09\x44\x5c\x37\x14\xb8\xb5\x81\x17\x28\x2a\x17\xd7\x4b\xaa\x00\x00\x00\x00\x4e\x4f\x8a\x26\xc4\xce\x8d\x1e\x00\x01\xb2\x05\x82\x0a\x00\x00\xb5\x12\x70\x16\xb1\xc4\x67\xfb\x02\x00\x00\x00\x00\x04\x59\x5a", 728 },
	{ "1234567890abcdefghijklmnopqrstuvwxyz\n", "\xfd\x37\x7a\x58\x5a\x00\x00\x04\xe6\xd6\xb4\x46\x02\x00\x21\x01\x16\x00\x00\x00\x74\x2f\xe5\xa3\x01\x00\x24\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x0a\x00\x00\x00\x00\x93\xee\x03\x9e\xfe\xdb\xdd\xf8\x00\x01\x3d\x25\xd2\x29\x6a\x01\x1f\xb6\xf3\x7d\x01\x00\x00\x00\x00\x04\x59\x5a", 96 }
};

// Deflated data can be different based on the OS/system where the deflation was done.
// This ignore the OS byte in the zlib header
#define mu_deflated_eq(actual, expected, len, message) \
	do { \
		mu_assert_memeq(actual, expected, 9, message); \
		mu_assert_memeq(actual + 10, expected + 10, len - 10, message); \
	} while (0)

bool test_rz_lzma_dec(void) {
	for (int i = 0; i < 3; i++) {
		RzBuffer *deflated_buf = rz_buf_new_with_bytes((unsigned char *)test_cases[i].deflated, test_cases[i].deflated_length);
		RzBuffer *inflated_buf = rz_buf_new_empty(strlen(test_cases[i].inflated));
		mu_assert_true(rz_lzma_dec_buf(deflated_buf, inflated_buf, 1 << 13, NULL), "rz_lzma_dec_buf failed");
		char *inflated = calloc(strlen(test_cases[i].inflated) + 1, sizeof(char));
		rz_buf_read_at(inflated_buf, 0, (ut8 *)inflated, strlen(test_cases[i].inflated));

		mu_assert_notnull(inflated, "rz_buf_read failed");
		mu_assert_streq(inflated, test_cases[i].inflated, "rz_lzma_dec_buf does not return expected output");
		free(inflated);
		rz_buf_free(deflated_buf);
		rz_buf_free(inflated_buf);
	}

	mu_end;
}

bool test_rz_lzma_enc(void) {
	for (int i = 0; i < 3; i++) {
		RzBuffer *inflated_buf = rz_buf_new_with_string(test_cases[i].inflated);
		RzBuffer *deflated_buf = rz_buf_new_empty(test_cases[i].deflated_length);
		mu_assert_true(rz_lzma_enc_buf(inflated_buf, deflated_buf, 1 << 18, NULL), "rz_lzma_enc_buf failed");
		unsigned char *deflated = malloc(test_cases[i].deflated_length);
		rz_buf_read_at(deflated_buf, 0, deflated, test_cases[i].deflated_length);
		mu_deflated_eq(deflated, (unsigned char *)test_cases[i].deflated, test_cases[i].deflated_length, "rz_lzma_enc_buf does not return expected output");
		free(deflated);
		rz_buf_free(inflated_buf);
		rz_buf_free(deflated_buf);
	}

	mu_end;
}

// LZMA-alone (legacy LZMA1, .lzma) fixtures generated with: xz --format=lzma -c
// They share inflated payloads with the .xz cases above so the difference is
// purely the container header (0x5d... instead of the .xz magic).
struct {
	const char *inflated;
	const unsigned char deflated[160];
	size_t deflated_length;
} test_cases_alone[] = {
	{ "1234567890abcdefghijklmnopqrstuvwxyz\n",
		{ 0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0x00, 0x18, 0x8c, 0x82, 0xb6, 0xc4, 0x11, 0x34, 0x5c, 0x4e, 0xe1,
			0xd6, 0x5e, 0xd1, 0x46, 0xf2, 0x6e, 0xf8, 0x5b, 0xdf, 0x60, 0xc9, 0x34,
			0x08, 0x05, 0x5f, 0xa3, 0xc3, 0x5d, 0x4c, 0xe6, 0xcd, 0x2d, 0x81, 0x37,
			0xfe, 0x2c, 0x76, 0xd3, 0x09, 0xe7, 0xff, 0xff, 0xc9, 0x9d, 0x00, 0x00 },
		60 },
	{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n",
		{ 0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0x00, 0x26, 0x1b, 0xca, 0x46, 0x67, 0x5a, 0xf2, 0x77, 0xb8, 0x7d,
			0x86, 0xd8, 0x41, 0xdb, 0x05, 0x35, 0xcd, 0x83, 0xa5, 0x7c, 0x12, 0xa5,
			0x05, 0xdb, 0x90, 0xbd, 0x2f, 0x14, 0xd3, 0x71, 0x72, 0x96, 0xa8, 0x8a,
			0x7d, 0x84, 0x56, 0x71, 0x8d, 0x6a, 0x22, 0x98, 0xab, 0x9e, 0x3d, 0xc3,
			0x55, 0xef, 0xcc, 0xa5, 0xc3, 0xdd, 0x5b, 0x8e, 0xbf, 0x03, 0x81, 0x21,
			0x40, 0xd6, 0x26, 0x91, 0x02, 0x45, 0x4f, 0x92, 0xa1, 0x78, 0xbb, 0x8a,
			0x00, 0xaf, 0x90, 0x2a, 0x26, 0x92, 0x02, 0x23, 0xe5, 0x5c, 0xb3, 0x2d,
			0xe3, 0xe8, 0x5c, 0x2c, 0xfb, 0x32, 0x21, 0xc6, 0x6f, 0x6a, 0x37, 0xb1,
			0x66, 0x20, 0xcd, 0xb7, 0x52, 0x7d, 0x66, 0xa4, 0x21, 0x08, 0xd1, 0x44,
			0x0f, 0x7f, 0xad, 0x0d, 0x64, 0xff, 0xed, 0x34, 0x38, 0x00 },
		130 },
};

bool test_rz_lzma_alone_dec(void) {
	// Scenario 1: happy path. Two pre-compressed fixtures must round-trip
	// byte-for-byte against their original inflated payloads. Uses a default
	// block size large enough to consume the whole stream in one inner pass.
	for (size_t i = 0; i < RZ_ARRAY_SIZE(test_cases_alone); i++) {
		size_t inflated_len = strlen(test_cases_alone[i].inflated);
		RzBuffer *src = rz_buf_new_with_bytes(test_cases_alone[i].deflated, test_cases_alone[i].deflated_length);
		RzBuffer *dst = rz_buf_new_empty(inflated_len);
		mu_assert_true(rz_lzma_alone_dec_buf(src, dst, 1 << 13), "rz_lzma_alone_dec_buf failed on valid fixture");
		mu_assert_eq(rz_buf_size(dst), inflated_len, "decompressed size mismatch");
		char *out = calloc(inflated_len + 1, 1);
		rz_buf_read_at(dst, 0, (ut8 *)out, inflated_len);
		mu_assert_streq(out, test_cases_alone[i].inflated, "decompressed payload mismatch");
		free(out);
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	// Scenario 2: tiny block_size forces the inner while-loop to iterate many
	// times (re-fill input, re-flush output). Exercises the multi-pass code
	// path that the large block_size in scenario 1 hides.
	{
		size_t inflated_len = strlen(test_cases_alone[1].inflated);
		RzBuffer *src = rz_buf_new_with_bytes(test_cases_alone[1].deflated, test_cases_alone[1].deflated_length);
		RzBuffer *dst = rz_buf_new_empty(inflated_len);
		mu_assert_true(rz_lzma_alone_dec_buf(src, dst, 16), "rz_lzma_alone_dec_buf failed with small block_size");
		char *out = calloc(inflated_len + 1, 1);
		rz_buf_read_at(dst, 0, (ut8 *)out, inflated_len);
		mu_assert_streq(out, test_cases_alone[1].inflated, "small-block decompression does not match original");
		free(out);
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	// Scenario 3: random garbage bytes are not a valid LZMA1 stream. The
	// decoder must fail cleanly (return false) rather than crash or produce
	// uninitialized output.
	{
		const ut8 garbage[] = {
			0xDE, 0xAD, 0xBE, 0xEF, 0x42, 0x42, 0x42, 0x42,
			0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF
		};
		RzBuffer *src = rz_buf_new_with_bytes(garbage, sizeof(garbage));
		RzBuffer *dst = rz_buf_new_empty(0);
		mu_assert_false(rz_lzma_alone_dec_buf(src, dst, 1 << 13), "garbage input must be rejected");
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	// Scenario 4: empty input buffer triggers the "src_readlen == 0" branch
	// immediately, which switches the action to LZMA_FINISH on an unprimed
	// stream. Decoder must fail without UB or crash.
	{
		RzBuffer *src = rz_buf_new_empty(0);
		RzBuffer *dst = rz_buf_new_empty(0);
		mu_assert_false(rz_lzma_alone_dec_buf(src, dst, 1 << 13), "empty input must be rejected");
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	mu_end;
}

// --- Fault injection for lzma_alone_action_buf error-handling branches ---

// A buffer back-end whose every operation reports failure. Used as the
// source to trigger the "src_readlen < 0" branch on the first read attempt.
static st64 failing_read(RZ_BORROW RzBuffer *b, RZ_OUT ut8 *buf, ut64 len) {
	(void)b;
	(void)buf;
	(void)len;
	return -1;
}
static const RzBufferMethods failing_src_methods = {
	.read = failing_read,
};

// A buffer back-end that accepts no writes. Used as the destination to
// trigger the "rz_buf_write(dst, ...) != write_size" branch after the
// decoder produces its first decompressed chunk.
static st64 failing_write(RzBuffer *b, const ut8 *buf, ut64 len) {
	(void)b;
	(void)buf;
	(void)len;
	return -1;
}
static const RzBufferMethods failing_dst_methods = {
	.write = failing_write,
};

bool test_rz_lzma_alone_dec_error_paths(void) {
	// Branch: `if (src_readlen < 0)` — the first rz_buf_read_at on the
	// source returns -1, so the decoder bails out before processing anything.
	{
		RzBuffer *src = rz_buf_new_with_methods(&failing_src_methods, NULL, RZ_BUFFER_CUSTOM);
		RzBuffer *dst = rz_buf_new_empty(0);
		mu_assert_notnull(src, "custom failing-src buffer creation failed");
		mu_assert_false(rz_lzma_alone_dec_buf(src, dst, 1 << 13),
			"src read returning -1 must abort decompression");
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	// Branch: `if (rz_buf_write(dst, outbuf, write_size) != write_size)` —
	// a valid LZMA-alone stream feeds the decoder, the decoder emits
	// decompressed bytes, but the destination buffer's write method always
	// returns -1, so the loop must bail out.
	{
		RzBuffer *src = rz_buf_new_with_bytes(test_cases_alone[0].deflated, test_cases_alone[0].deflated_length);
		RzBuffer *dst = rz_buf_new_with_methods(&failing_dst_methods, NULL, RZ_BUFFER_CUSTOM);
		mu_assert_notnull(dst, "custom failing-dst buffer creation failed");
		mu_assert_false(rz_lzma_alone_dec_buf(src, dst, 1 << 13),
			"dst write failure must abort decompression");
		rz_buf_free(src);
		rz_buf_free(dst);
	}

	mu_end;
}

int all_tests() {
	mu_run_test(test_rz_lzma_dec);
	mu_run_test(test_rz_lzma_enc);
	mu_run_test(test_rz_lzma_alone_dec);
	mu_run_test(test_rz_lzma_alone_dec_error_paths);

	return tests_passed != tests_run;
}

mu_main(all_tests);
