summaryrefslogtreecommitdiffstats
path: root/tests/test_lzip_decoder.c
blob: 3743d43412177c01836907ac8ad39225f525413c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
///////////////////////////////////////////////////////////////////////////////
//
/// \file       test_lzip_decoder.c
/// \brief      Tests decoding lzip data
//
//  Author:     Jia Tan
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include "tests.h"

#ifdef HAVE_LZIP_DECODER

// Memlimit large enough to pass all of the test files
#define MEMLIMIT (1U << 20)
#define DECODE_CHUNK_SIZE 1024


// The uncompressed data in the test files are short US-ASCII strings.
// The tests check if the decompressed output is what it is expected to be.
// Storing the strings here as text would break the tests on EBCDIC systems
// and storing the strings as an array of hex values is inconvenient, so
// store the CRC32 values of the expected data instead.
//
// CRC32 value of "Hello\nWorld\n"
static const uint32_t hello_world_crc = 0x15A2A343;

// CRC32 value of "Trailing garbage\n"
static const uint32_t trailing_garbage_crc = 0x87081A60;


// Helper function to decode a good file with no flags and plenty high memlimit
static void
basic_lzip_decode(const char *src, const uint32_t expected_crc)
{
	size_t file_size;
	uint8_t *data = tuktest_file_from_srcdir(src, &file_size);
	uint32_t checksum = 0;

	lzma_stream strm = LZMA_STREAM_INIT;
	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0), LZMA_OK);

	uint8_t *output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);

	strm.next_in = data;
	strm.next_out = output_buffer;
	strm.avail_out = DECODE_CHUNK_SIZE;

	// Feed 1 byte at a time to the decoder to look for any bugs
	// when switching between decoding sequences
	lzma_ret ret = LZMA_OK;
	while (ret == LZMA_OK) {
		strm.avail_in = 1;
		ret = lzma_code(&strm, LZMA_RUN);
		if (strm.avail_out == 0) {
			checksum = lzma_crc32(output_buffer,
				(size_t)(strm.next_out - output_buffer),
				checksum);
			// No need to free output_buffer because it will
			// automatically be freed at the end of the test by
			// tuktest.
			output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
			strm.next_out = output_buffer;
			strm.avail_out = DECODE_CHUNK_SIZE;
		}
	}

	assert_lzma_ret(ret, LZMA_STREAM_END);
	assert_uint_eq(strm.total_in, file_size);

	checksum = lzma_crc32(output_buffer,
			(size_t)(strm.next_out - output_buffer),
			checksum);
	assert_uint_eq(checksum, expected_crc);

	lzma_end(&strm);
}


static void
test_options(void)
{
	// Test NULL stream
	assert_lzma_ret(lzma_lzip_decoder(NULL, MEMLIMIT, 0),
			LZMA_PROG_ERROR);

	// Test invalid flags
	lzma_stream strm = LZMA_STREAM_INIT;
	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, UINT32_MAX),
			LZMA_OPTIONS_ERROR);
	// Memlimit tests are done elsewhere
}


static void
test_v0_decode(void)
{
	// This tests if liblzma can decode lzip version 0 files.
	// lzip 1.17 and older can decompress this, but lzip 1.18
	// and newer can no longer decode these files.
	basic_lzip_decode("files/good-1-v0.lz", hello_world_crc);
}


static void
test_v1_decode(void)
{
	// This tests decoding a basic lzip v1 file
	basic_lzip_decode("files/good-1-v1.lz", hello_world_crc);
}


// Helper function to decode a good file with trailing bytes after
// the lzip stream
static void
trailing_helper(const char *src, const uint32_t expected_data_checksum,
		const uint32_t expected_trailing_checksum)
{
	size_t file_size;
	uint32_t checksum = 0;
	uint8_t *data = tuktest_file_from_srcdir(src, &file_size);
	lzma_stream strm = LZMA_STREAM_INIT;
	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	uint8_t *output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);

	strm.next_in = data;
	strm.next_out = output_buffer;
	strm.avail_in = file_size;
	strm.avail_out = DECODE_CHUNK_SIZE;

	lzma_ret ret = LZMA_OK;
	while (ret == LZMA_OK) {
		ret = lzma_code(&strm, LZMA_RUN);
		if (strm.avail_out == 0) {
			checksum = lzma_crc32(output_buffer,
				(size_t)(strm.next_out - output_buffer),
				checksum);
			// No need to free output_buffer because it will
			// automatically be freed at the end of the test by
			// tuktest.
			output_buffer = tuktest_malloc(DECODE_CHUNK_SIZE);
			strm.next_out = output_buffer;
			strm.avail_out = DECODE_CHUNK_SIZE;
		}
	}

	assert_lzma_ret(ret, LZMA_STREAM_END);
	assert_uint(strm.total_in, <, file_size);

	checksum = lzma_crc32(output_buffer,
			(size_t)(strm.next_out - output_buffer),
			checksum);

	assert_uint_eq(checksum, expected_data_checksum);

	// Trailing data should be readable from strm.next_in
	checksum = lzma_crc32(strm.next_in, strm.avail_in, 0);
	assert_uint_eq(checksum, expected_trailing_checksum);

	lzma_end(&strm);
}


// Helper function to decode a bad file and compare to returned error to
// what the caller expects
static void
decode_expect_error(const char *src, lzma_ret expected_error)
{
	lzma_stream strm = LZMA_STREAM_INIT;
	size_t file_size;
	uint8_t *data = tuktest_file_from_srcdir(src, &file_size);

	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	uint8_t output_buffer[DECODE_CHUNK_SIZE];

	strm.avail_in = file_size;
	strm.next_in = data;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	lzma_ret ret = LZMA_OK;

	while (ret == LZMA_OK) {
		// Discard output since we are only looking for errors
		strm.next_out = output_buffer;
		strm.avail_out = DECODE_CHUNK_SIZE;
		if (strm.avail_in == 0)
			ret = lzma_code(&strm, LZMA_FINISH);
		else
			ret = lzma_code(&strm, LZMA_RUN);
	}

	assert_lzma_ret(ret, expected_error);
	lzma_end(&strm);
}


static void
test_v0_trailing(void)
{
	trailing_helper("files/good-1-v0-trailing-1.lz", hello_world_crc,
			trailing_garbage_crc);
}


static void
test_v1_trailing(void)
{
	trailing_helper("files/good-1-v1-trailing-1.lz", hello_world_crc,
			trailing_garbage_crc);

	// The second files/good-1-v1-trailing-2.lz will have the same
	// expected output and trailing output as
	// files/good-1-v1-trailing-1.lz, but this tests if the prefix
	// to the trailing data contains lzip magic bytes.
	// When this happens, the expected behavior is to silently ignore
	// the magic byte prefix and consume it from the input file.
	trailing_helper("files/good-1-v1-trailing-2.lz", hello_world_crc,
			trailing_garbage_crc);

	// Expect LZMA_BUF error if a file ends with the lzip magic bytes
	// but does not contain any data after
	decode_expect_error("files/bad-1-v1-trailing-magic.lz",
			LZMA_BUF_ERROR);
}


static void
test_concatentated(void)
{
	// First test a file with one v0 member and one v1 member
	// The first member should contain "Hello\n" and
	// the second member should contain "World!\n"

	lzma_stream strm = LZMA_STREAM_INIT;
	size_t file_size;
	uint8_t *v0_v1 = tuktest_file_from_srcdir("files/good-2-v0-v1.lz",
		&file_size);

	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	uint8_t output_buffer[DECODE_CHUNK_SIZE];

	strm.avail_in = file_size;
	strm.next_in = v0_v1;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);

	assert_uint_eq(strm.total_in, file_size);

	uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0);
	assert_uint_eq(checksum, hello_world_crc);

	// The second file contains one v1 member and one v2 member
	uint8_t *v1_v0 = tuktest_file_from_srcdir("files/good-2-v1-v0.lz",
		&file_size);

	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	strm.avail_in = file_size;
	strm.next_in = v1_v0;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);

	assert_uint_eq(strm.total_in, file_size);
	checksum = lzma_crc32(output_buffer, strm.total_out, 0);
	assert_uint_eq(checksum, hello_world_crc);

	// The third file contains 2 v1 members
	uint8_t *v1_v1 = tuktest_file_from_srcdir("files/good-2-v1-v1.lz",
		&file_size);

	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	strm.avail_in = file_size;
	strm.next_in = v1_v1;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);

	assert_uint_eq(strm.total_in, file_size);
	checksum = lzma_crc32(output_buffer, strm.total_out, 0);
	assert_uint_eq(checksum, hello_world_crc);

	lzma_end(&strm);
}


static void
test_crc(void)
{
	// Test invalid checksum
	lzma_stream strm = LZMA_STREAM_INIT;
	size_t file_size;
	uint8_t *data = tuktest_file_from_srcdir("files/bad-1-v1-crc32.lz",
			&file_size);

	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED), LZMA_OK);

	uint8_t output_buffer[DECODE_CHUNK_SIZE];

	strm.avail_in = file_size;
	strm.next_in = data;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR);

	// Test ignoring the checksum value - should decode successfully
	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED | LZMA_IGNORE_CHECK), LZMA_OK);

	strm.avail_in = file_size;
	strm.next_in = data;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);
	assert_uint_eq(strm.total_in, file_size);

	// Test tell check
	assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT,
			LZMA_CONCATENATED | LZMA_TELL_ANY_CHECK), LZMA_OK);

	strm.avail_in = file_size;
	strm.next_in = data;
	strm.avail_out = DECODE_CHUNK_SIZE;
	strm.next_out = output_buffer;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_GET_CHECK);
	assert_uint_eq(lzma_get_check(&strm), LZMA_CHECK_CRC32);
	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR);
	lzma_end(&strm);
}


static void
test_invalid_magic_bytes(void)
{
	uint8_t lzip_id_string[] = { 0x4C, 0x5A, 0x49, 0x50 };
	lzma_stream strm = LZMA_STREAM_INIT;

	for (uint32_t i = 0; i < ARRAY_SIZE(lzip_id_string); i++) {
		// Corrupt magic bytes
		lzip_id_string[i] ^= 1;
		uint8_t output_buffer[DECODE_CHUNK_SIZE];

		assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0),
				LZMA_OK);

		strm.next_in = lzip_id_string;
		strm.avail_in = sizeof(lzip_id_string);
		strm.next_out = output_buffer;
		strm.avail_out = DECODE_CHUNK_SIZE;

		assert_lzma_ret(lzma_code(&strm, LZMA_RUN),
				LZMA_FORMAT_ERROR);

		// Reset magic bytes
		lzip_id_string[i] ^= 1;
	}

	lzma_end(&strm);
}


static void
test_invalid_version(void)
{
	// The file contains a version number that is not 0 or 1,
	// so it should cause an error
	decode_expect_error("files/unsupported-1-v234.lz",
			LZMA_OPTIONS_ERROR);
}


static void
test_invalid_dictionary_size(void)
{
	// First file has too small dictionary size field
	decode_expect_error("files/bad-1-v1-dict-1.lz", LZMA_DATA_ERROR);

	// Second file has too large dictionary size field
	decode_expect_error("files/bad-1-v1-dict-2.lz", LZMA_DATA_ERROR);
}


static void
test_invalid_uncomp_size(void)
{
	// Test invalid v0 lzip file uncomp size
	decode_expect_error("files/bad-1-v0-uncomp-size.lz",
			LZMA_DATA_ERROR);

	// Test invalid v1 lzip file uncomp size
	decode_expect_error("files/bad-1-v1-uncomp-size.lz",
			LZMA_DATA_ERROR);
}


static void
test_invalid_member_size(void)
{
	decode_expect_error("files/bad-1-v1-member-size.lz",
			LZMA_DATA_ERROR);
}


static void
test_invalid_memlimit(void)
{
	// A very low memlimit should prevent decoding.
	// Should be able to update the memlimit after failing
	size_t file_size;
	uint8_t *data = tuktest_file_from_srcdir("files/good-1-v1.lz",
			&file_size);

	uint8_t output_buffer[DECODE_CHUNK_SIZE];

	lzma_stream strm = LZMA_STREAM_INIT;

	assert_lzma_ret(lzma_lzip_decoder(&strm, 1, 0), LZMA_OK);

	strm.next_in = data;
	strm.avail_in = file_size;
	strm.next_out = output_buffer;
	strm.avail_out = DECODE_CHUNK_SIZE;

	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_MEMLIMIT_ERROR);

	// Up the memlimit so decoding can continue.
	// First only increase by a small amount and expect an error
	assert_lzma_ret(lzma_memlimit_set(&strm, 100), LZMA_MEMLIMIT_ERROR);
	assert_lzma_ret(lzma_memlimit_set(&strm, MEMLIMIT), LZMA_OK);

	// Finish decoding
	assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END);

	assert_uint_eq(strm.total_in, file_size);
	uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0);
	assert_uint_eq(checksum, hello_world_crc);

	lzma_end(&strm);
}
#endif


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

#ifndef HAVE_LZIP_DECODER
	tuktest_early_skip("lzip decoder disabled");
#else
	tuktest_run(test_options);
	tuktest_run(test_v0_decode);
	tuktest_run(test_v1_decode);
	tuktest_run(test_v0_trailing);
	tuktest_run(test_v1_trailing);
	tuktest_run(test_concatentated);
	tuktest_run(test_crc);
	tuktest_run(test_invalid_magic_bytes);
	tuktest_run(test_invalid_version);
	tuktest_run(test_invalid_dictionary_size);
	tuktest_run(test_invalid_uncomp_size);
	tuktest_run(test_invalid_member_size);
	tuktest_run(test_invalid_memlimit);
	return tuktest_end();
#endif

}