1 /* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "buffer.h"
5 #include "istream.h"
6 #include "iostream-temp.h"
7 #include "ostream.h"
8 #include "sha1.h"
9 #include "randgen.h"
10 #include "test-common.h"
11 #include "compression.h"
12 #include "iostream-lz4.h"
13 
14 #include "hex-binary.h"
15 
16 #include <unistd.h>
17 #include <fcntl.h>
18 
test_compression_handler_detect(const struct compression_handler * handler)19 static void test_compression_handler_detect(const struct compression_handler *handler)
20 {
21 	const unsigned char test_data[] = {'h','e','l','l','o',' ',
22 					   'w','o','r','l','d','\n'};
23 	const unsigned char *data;
24 	size_t size;
25 	buffer_t *buffer;
26 	struct ostream *test_output;
27 	struct ostream *output;
28 
29 	struct istream *test_input;
30 	struct istream *input;
31 
32 	/* write some amount of data */
33 	test_begin(t_strdup_printf("compression handler %s (detect)", handler->name));
34 
35 	buffer = buffer_create_dynamic(default_pool, 1024);
36 
37 	test_output = test_ostream_create(buffer);
38 	output = handler->create_ostream(test_output, 1);
39 	o_stream_unref(&test_output);
40 
41 	/* write data at once */
42 	test_assert(o_stream_send(output, test_data, sizeof(test_data)) == sizeof(test_data));
43 	test_assert(o_stream_finish(output) == 1);
44 	o_stream_unref(&output);
45 
46 	test_input = test_istream_create_data(buffer->data, buffer->used);
47 	handler = compression_detect_handler(test_input);
48 	i_stream_seek(test_input, 0);
49 	test_assert(handler != NULL);
50 	if (handler != NULL) {
51 		input = handler->create_istream(test_input);
52 		i_stream_unref(&test_input);
53 
54 		test_assert(i_stream_read_more(input, &data, &size) > 0);
55 		test_assert(size == sizeof(test_data) &&
56 			    memcmp(data, test_data, size) == 0);
57 
58 		i_stream_unref(&input);
59 	} else {
60 		i_stream_unref(&test_input);
61 	}
62 
63 	buffer_free(&buffer);
64 	test_end();
65 }
66 
67 static void
test_compression_handler_short(const struct compression_handler * handler,bool autodetect)68 test_compression_handler_short(const struct compression_handler *handler,
69 			       bool autodetect)
70 {
71 	const unsigned char *data;
72 	size_t len, size;
73 	buffer_t *test_data;
74 	buffer_t *buffer;
75 	struct ostream *test_output;
76 	struct ostream *output;
77 
78 	struct istream *test_input;
79 	struct istream *input;
80 
81 	/* write some amount of data */
82 	test_begin(t_strdup_printf("compression handler %s (small, autodetect=%s)",
83 				   handler->name, autodetect ? "yes" : "no"));
84 	len = i_rand_minmax(1, 1024);
85 	test_data = buffer_create_dynamic(default_pool, len);
86 	random_fill(buffer_append_space_unsafe(test_data, len), len);
87 	buffer_set_used_size(test_data, len);
88 	buffer_append(test_data, "hello. world.\n", 14);
89 
90 	buffer = buffer_create_dynamic(default_pool, 1024);
91 	test_output = test_ostream_create(buffer);
92 	output = handler->create_ostream(test_output, 1);
93 	o_stream_unref(&test_output);
94 
95 	/* write data at once */
96 	test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
97 	test_assert(o_stream_finish(output) == 1);
98 	o_stream_unref(&output);
99 
100 	/* read data at once */
101 	test_input = test_istream_create_data(buffer->data, buffer->used);
102 	input = !autodetect ? handler->create_istream(test_input) :
103 		i_stream_create_decompress(test_input, 0);
104 	i_stream_unref(&test_input);
105 
106 	test_assert(i_stream_read_more(input, &data, &size) > 0);
107 	test_assert(size == test_data->used &&
108 		    memcmp(data, test_data->data, size) ==0);
109 
110 	i_stream_unref(&input);
111 
112 	buffer_free(&buffer);
113 	buffer_free(&test_data);
114 
115 	test_end();
116 }
117 
118 static void
test_compression_handler_empty(const struct compression_handler * handler,bool autodetect)119 test_compression_handler_empty(const struct compression_handler *handler,
120 			       bool autodetect)
121 {
122 	buffer_t *buffer;
123 	struct ostream *test_output;
124 	struct ostream *output;
125 
126 	struct istream *test_input;
127 	struct istream *input;
128 
129 	/* create stream and finish it without writing anything */
130 	test_begin(t_strdup_printf("compression handler %s (empty, autodetect=%s)",
131 				   handler->name, autodetect ? "yes" : "no"));
132 	buffer = buffer_create_dynamic(default_pool, 128);
133 	test_output = test_ostream_create(buffer);
134 	output = handler->create_ostream(test_output, 1);
135 	o_stream_unref(&test_output);
136 	test_assert(o_stream_finish(output) == 1);
137 	o_stream_unref(&output);
138 
139 	/* read the input */
140 	test_input = test_istream_create_data(buffer->data, buffer->used);
141 	input = !autodetect ? handler->create_istream(test_input) :
142 		i_stream_create_decompress(test_input, 0);
143 	i_stream_unref(&test_input);
144 
145 	test_assert(i_stream_read(input) == -1);
146 	test_assert(i_stream_get_data_size(input) == 0);
147 	i_stream_unref(&input);
148 
149 	buffer_free(&buffer);
150 
151 	test_end();
152 }
153 
154 static void
test_compression_handler_seek(const struct compression_handler * handler,bool autodetect)155 test_compression_handler_seek(const struct compression_handler *handler,
156 			      bool autodetect)
157 {
158 	const unsigned char *data,*ptr;
159 	size_t len, size, pos;
160 	buffer_t *test_data;
161 	buffer_t *buffer;
162 	struct ostream *test_output;
163 	struct ostream *output;
164 
165 	struct istream *test_input;
166 	struct istream *input;
167 
168 	/* write some amount of data */
169 	test_begin(t_strdup_printf("compression handler %s (seek, autodetect=%s)",
170 				   handler->name, autodetect ? "yes" : "no"));
171 	len = i_rand_minmax(1024, 2048);
172 	test_data = buffer_create_dynamic(default_pool, len);
173 	random_fill(buffer_append_space_unsafe(test_data, len), len);
174 	buffer_set_used_size(test_data, len);
175 	buffer_append(test_data, "hello. world.\n", 14);
176 
177 	buffer = buffer_create_dynamic(default_pool, 1024);
178 	test_output = test_ostream_create(buffer);
179 	output = handler->create_ostream(test_output, 1);
180 	o_stream_unref(&test_output);
181 
182 	/* write data at once */
183 	test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
184 	test_assert(o_stream_finish(output) == 1);
185 	o_stream_unref(&output);
186 
187 	test_input = test_istream_create_data(buffer->data, buffer->used);
188 	input = !autodetect ? handler->create_istream(test_input) :
189 		i_stream_create_decompress(test_input, 0);
190 	i_stream_unref(&test_input);
191 
192 	/* seek forward */
193 	i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */
194 
195 	test_assert(i_stream_read_more(input, &data, &size) > 0);
196 	test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0);
197 	i_stream_skip(input, size);
198 
199 	ptr = test_data->data;
200 
201 	/* seek to random positions and see that we get correct data */
202 	for (unsigned int i = 0; i < 1000; i++) {
203 		pos = i_rand_limit(test_data->used);
204 		i_stream_seek(input, pos);
205 		size = 0;
206 		test_assert_idx(i_stream_read_more(input, &data, &size) > 0, i);
207 		test_assert_idx(size > 0 && memcmp(data,ptr+pos,size) == 0, i);
208 	}
209 
210 	i_stream_unref(&input);
211 
212 	buffer_free(&buffer);
213 	buffer_free(&test_data);
214 
215 	test_end();
216 }
217 
218 static void
test_compression_handler_reset(const struct compression_handler * handler,bool autodetect)219 test_compression_handler_reset(const struct compression_handler *handler,
220 			       bool autodetect)
221 {
222 	const unsigned char *data;
223 	size_t len, size;
224 	buffer_t *test_data;
225 	buffer_t *buffer;
226 	struct ostream *test_output;
227 	struct ostream *output;
228 
229 	struct istream *test_input;
230 	struct istream *input;
231 
232 	/* write some amount of data */
233 	test_begin(t_strdup_printf("compression handler %s (reset, autodetect=%s)",
234 				   handler->name, autodetect ? "yes" : "no"));
235 	len = i_rand_minmax(1024, 2048);
236 	test_data = buffer_create_dynamic(default_pool, len);
237 	random_fill(buffer_append_space_unsafe(test_data, len), len);
238 	buffer_set_used_size(test_data, len);
239 	buffer_append(test_data, "hello. world.\n", 14);
240 
241 	buffer = buffer_create_dynamic(default_pool, 1024);
242 	test_output = test_ostream_create(buffer);
243 	output = handler->create_ostream(test_output, 1);
244 	o_stream_unref(&test_output);
245 
246 	/* write data at once */
247 	test_assert(o_stream_send(output, test_data->data, test_data->used) == (ssize_t)test_data->used);
248 	test_assert(o_stream_finish(output) == 1);
249 	o_stream_unref(&output);
250 
251 	test_input = test_istream_create_data(buffer->data, buffer->used);
252 	input = !autodetect ? handler->create_istream(test_input) :
253 		i_stream_create_decompress(test_input, 0);
254 	i_stream_unref(&test_input);
255 
256 	/* seek forward */
257 	i_stream_seek(input, test_data->used - 14); /* should read 'hello. world.\n' */
258 
259 	test_assert(i_stream_read_more(input, &data, &size) > 0);
260 	test_assert(size >= 14 && memcmp(data, "hello. world.\n", 14) == 0);
261 	i_stream_skip(input, size);
262 
263 	/* reset */
264 	i_stream_sync(input);
265 
266 	/* see that we still get data, at start */
267 	size = 0;
268 	test_assert(i_stream_read_more(input, &data, &size) > 0);
269 	test_assert(size > 0 && memcmp(data, test_data->data, size) == 0);
270 
271 	i_stream_unref(&input);
272 
273 	buffer_free(&buffer);
274 	buffer_free(&test_data);
275 
276 	test_end();
277 }
278 
279 static void
test_compression_handler(const struct compression_handler * handler,bool autodetect)280 test_compression_handler(const struct compression_handler *handler,
281 			 bool autodetect)
282 {
283 	const char *path = "test-compression.tmp";
284 	struct istream *file_input, *input;
285 	struct ostream *file_output, *output;
286 	unsigned char buf[IO_BLOCK_SIZE];
287 	const unsigned char *data;
288 	size_t size;
289 	uoff_t stream_size;
290 	struct sha1_ctxt sha1;
291 	unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN];
292 	unsigned int i;
293 	int fd;
294 	ssize_t ret;
295 
296 	test_begin(t_strdup_printf("compression handler %s (autodetect=%s)",
297 				   handler->name, autodetect ? "yes" : "no"));
298 
299 	/* write compressed data */
300 	fd = open(path, O_TRUNC | O_CREAT | O_RDWR, 0600);
301 	if (fd == -1)
302 		i_fatal("creat(%s) failed: %m", path);
303 	file_output = o_stream_create_fd_file(fd, 0, FALSE);
304 	output = handler->create_ostream(file_output, 1);
305 	sha1_init(&sha1);
306 
307 	/* 1) write lots of easily compressible data */
308 	memset(buf, 0, sizeof(buf));
309 	for (i = 0; i < 1024*1024*4 / sizeof(buf); i++) {
310 		sha1_loop(&sha1, buf, sizeof(buf));
311 		test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
312 	}
313 
314 	/* 2) write uncompressible data */
315 	for (i = 0; i < 1024*128 / sizeof(buf); i++) {
316 		random_fill(buf, sizeof(buf));
317 		sha1_loop(&sha1, buf, sizeof(buf));
318 		test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
319 	}
320 	/* make sure the input size isn't multiple of something simple */
321 	random_fill(buf, sizeof(buf));
322 	sha1_loop(&sha1, buf, sizeof(buf) - 5);
323 	test_assert(o_stream_send(output, buf, sizeof(buf) - 5) == sizeof(buf) - 5);
324 
325 	/* 3) write semi-compressible data */
326 	for (i = 0; i < sizeof(buf); i++) {
327 		if (i_rand_limit(3) == 0)
328 			buf[i] = i_rand_limit(4);
329 		else
330 			buf[i] = i & UCHAR_MAX;
331 	}
332 	for (i = 0; i < 1024*128 / sizeof(buf); i++) {
333 		sha1_loop(&sha1, buf, sizeof(buf));
334 		test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
335 	}
336 
337 	test_assert(o_stream_finish(output) > 0);
338 	uoff_t uncompressed_size = output->offset;
339 	o_stream_destroy(&output);
340 	uoff_t compressed_size = file_output->offset;
341 	o_stream_destroy(&file_output);
342 	sha1_result(&sha1, output_sha1);
343 
344 	/* read and uncompress the data */
345 	file_input = i_stream_create_fd(fd, IO_BLOCK_SIZE);
346 	input = !autodetect ? handler->create_istream(file_input) :
347 		i_stream_create_decompress(file_input, 0);
348 
349 	test_assert(i_stream_get_size(input, FALSE, &stream_size) == 1);
350 	test_assert(stream_size == compressed_size);
351 
352 	test_assert(i_stream_get_size(input, TRUE, &stream_size) == 1);
353 	test_assert(stream_size == uncompressed_size);
354 
355 	sha1_init(&sha1);
356 	for (bool seeked = FALSE;;) {
357 		sha1_init(&sha1);
358 		while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
359 			sha1_loop(&sha1, data, size);
360 			i_stream_skip(input, size);
361 		}
362 		test_assert(ret == -1);
363 		test_assert(input->stream_errno == 0);
364 		sha1_result(&sha1, input_sha1);
365 		test_assert(memcmp(input_sha1, output_sha1, sizeof(input_sha1)) == 0);
366 		if (seeked)
367 			break;
368 		seeked = TRUE;
369 		i_stream_seek(input, 1);
370 		(void)i_stream_read(input);
371 		i_stream_seek(input, 0);
372 	}
373 	i_stream_destroy(&input);
374 	i_stream_destroy(&file_input);
375 
376 	i_unlink(path);
377 	i_close_fd(&fd);
378 
379 	test_end();
380 }
381 
382 static void
test_compression_handler_partial_parent_write(const struct compression_handler * handler,bool autodetect)383 test_compression_handler_partial_parent_write(const struct compression_handler *handler,
384 					      bool autodetect)
385 {
386 	test_begin(t_strdup_printf("compression handler %s (partial parent writes, autodetect=%s)",
387 				   handler->name, autodetect ? "yes" : "no"));
388 
389 	int ret;
390 	buffer_t *buffer = t_buffer_create(64);
391 	buffer_t *compressed_data = t_buffer_create(256);
392 	struct ostream *os = test_ostream_create_nonblocking(buffer, 64);
393 	struct ostream *os_compressed = handler->create_ostream(os, 9);
394 	o_stream_unref(&os);
395 
396 	unsigned char input_buffer[64];
397 	/* create unlikely compressible data */
398 	random_fill(input_buffer, sizeof(input_buffer));
399 
400 	for (unsigned int i = 0; i < 10; i++) {
401 		/* write it to stream */
402 		test_assert_idx(o_stream_send(os_compressed, input_buffer, sizeof(input_buffer)) == sizeof(input_buffer), i);
403 
404 		while ((ret = o_stream_flush(os_compressed)) == 0) {
405 			/* flush buffer */
406 			if (buffer->used > 0)
407 				buffer_append(compressed_data, buffer->data, buffer->used);
408 			buffer_set_used_size(buffer, 0);
409 		}
410 		if (buffer->used > 0)
411 			buffer_append(compressed_data, buffer->data, buffer->used);
412 		buffer_set_used_size(buffer, 0);
413 		test_assert_idx(ret == 1, i);
414 	}
415 	test_assert(o_stream_finish(os_compressed) == 1);
416 	o_stream_unref(&os_compressed);
417         if (buffer->used > 0)
418                 buffer_append(compressed_data, buffer->data, buffer->used);
419 
420 	struct istream *is = test_istream_create_data(compressed_data->data, compressed_data->used);
421 	struct istream *is_decompressed =
422 		!autodetect ? handler->create_istream(is) :
423 		i_stream_create_decompress(is, 0);
424 	i_stream_unref(&is);
425 
426 	const unsigned char *data;
427 	size_t siz;
428 	buffer_t *decompressed_data = t_buffer_create(sizeof(input_buffer)*10);
429 
430 	while(i_stream_read_more(is_decompressed, &data, &siz) > 0) {
431 		buffer_append(decompressed_data, data, siz);
432 		i_stream_skip(is_decompressed, siz);
433 	}
434 	test_assert(decompressed_data->used == sizeof(input_buffer)*10);
435 	for(siz = 0; siz < decompressed_data->used; siz+=sizeof(input_buffer)) {
436 		test_assert(decompressed_data->used - siz >= sizeof(input_buffer) &&
437 			   memcmp(CONST_PTR_OFFSET(decompressed_data->data, siz),
438 				  input_buffer, sizeof(input_buffer)) == 0);
439 	}
440 
441 	i_stream_unref(&is_decompressed);
442 
443 	test_end();
444 }
445 
446 static void
test_compression_handler_random_io(const struct compression_handler * handler,bool autodetect)447 test_compression_handler_random_io(const struct compression_handler *handler,
448 				   bool autodetect)
449 {
450 	unsigned char in_buf[8192];
451 	size_t in_buf_size;
452 	buffer_t *enc_buf, *dec_buf;
453 	unsigned int i, j;
454 	int ret;
455 
456 	enc_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
457 	dec_buf = buffer_create_dynamic(default_pool, sizeof(in_buf));
458 
459 	test_begin(t_strdup_printf("compression handler %s (random I/O, autodetect=%s)",
460 				   handler->name, autodetect ? "yes" : "no"));
461 
462 	for (i = 0; !test_has_failed() && i < 300; i++) {
463 		struct istream *input1, *input2;
464 		struct ostream *output1, *output2;
465 		struct istream *top_input;
466 		const unsigned char *data;
467 		size_t size, in_pos, out_pos;
468 
469 		/* Initialize test data (semi-compressible) */
470 		in_buf_size = i_rand_limit(sizeof(in_buf));
471 		for (j = 0; j < in_buf_size; j++) {
472 			if (i_rand_limit(3) == 0)
473 				in_buf[j] = i_rand_limit(256);
474 			else
475 				in_buf[j] = (unsigned char)j;
476 		}
477 
478 		/* Reset encode output buffer */
479 		buffer_set_used_size(enc_buf, 0);
480 
481 		/* Create input stream for test data */
482 		input1 = test_istream_create_data(in_buf, in_buf_size);
483 		i_stream_set_name(input1, "[data]");
484 
485 		/* Create output stream for compressed data */
486 		output1 = test_ostream_create_nonblocking(enc_buf,
487 							  i_rand_minmax(1, 512));
488 
489 		/* Create compressor output stream */
490 		output2 = handler->create_ostream(output1, i_rand_minmax(1, 6));
491 
492 		/* Compress the data incrementally */
493 		in_pos = out_pos = 0;
494 		ret = 0;
495 		test_istream_set_size(input1, in_pos);
496 		while (ret == 0) {
497 			enum ostream_send_istream_result res;
498 
499 			res = o_stream_send_istream(output2, input1);
500 			switch(res) {
501 			case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
502 			case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
503 				ret = -1;
504 				break;
505 			case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
506 				out_pos += i_rand_limit(512);
507 				test_ostream_set_max_output_size(
508 					output1, out_pos);
509 				break;
510 			case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
511 				in_pos += i_rand_limit(512);
512 				if (in_pos > in_buf_size)
513 					in_pos = in_buf_size;
514 				test_istream_set_size(input1, in_pos);
515 				break;
516 			case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
517 				/* finish it */
518 				ret = o_stream_finish(output2);
519 				break;
520 			}
521 		}
522 
523 		/* Clean up */
524 		i_stream_unref(&input1);
525 		o_stream_unref(&output1);
526 		o_stream_unref(&output2);
527 
528 		/* Reset decode output buffer */
529 		buffer_set_used_size(dec_buf, 0);
530 
531 		/* Create input stream for compressed data */
532 		input1 = i_stream_create_from_buffer(enc_buf);
533 		i_stream_set_name(input1, "[compressed-data]");
534 
535 		/* Create decompressor stream */
536 		input2 = !autodetect ? handler->create_istream(input1) :
537 			i_stream_create_decompress(input1, 0);
538 		i_stream_set_name(input2, "[decompressor]");
539 
540 		/* Assign random buffer sizes */
541 		i_stream_set_max_buffer_size(input2, i_rand_minmax(1, 512));
542 
543 		/* Read the outer stream in full with random increments. */
544 		top_input = input2;
545 		while ((ret = i_stream_read_more(
546 			top_input, &data, &size)) > 0) {
547 			size_t ch = i_rand_limit(512);
548 
549 			size = I_MIN(size, ch);
550 			buffer_append(dec_buf, data, size);
551 			i_stream_skip(top_input, size);
552 		}
553 		if (ret < 0 && top_input->stream_errno == 0) {
554 			data = i_stream_get_data(top_input, &size);
555 			if (size > 0) {
556 				buffer_append(dec_buf, data, size);
557 				i_stream_skip(top_input, size);
558 			}
559 		}
560 
561 		/* Assert stream status */
562 		test_assert_idx(ret < 0 && top_input->stream_errno == 0, i);
563 		/* Assert input/output equality */
564 		test_assert_idx(dec_buf->used == in_buf_size &&
565 				memcmp(in_buf, dec_buf->data, in_buf_size) == 0,
566 				i);
567 
568 		if (top_input->stream_errno != 0) {
569 			i_error("%s: %s", i_stream_get_name(input1),
570 			       i_stream_get_error(input1));
571 			i_error("%s: %s", i_stream_get_name(input2),
572 			       i_stream_get_error(input2));
573 		}
574 
575 		if (test_has_failed()) {
576 			i_info("Test parameters: size=%zu",
577 				in_buf_size);
578 		}
579 
580 		/* Clean up */
581 		i_stream_unref(&input1);
582 		i_stream_unref(&input2);
583 	}
584 	test_end();
585 
586 	buffer_free(&enc_buf);
587 	buffer_free(&dec_buf);
588 }
589 
590 static void
test_compression_handler_large_random_io(const struct compression_handler * handler,bool autodetect)591 test_compression_handler_large_random_io(const struct compression_handler *handler,
592 					 bool autodetect)
593 {
594 #define RANDOMNESS_SIZE (1024*1024)
595 	unsigned char *randomness;
596 	struct istream *input, *dec_input;
597 	struct ostream *temp_output, *output;
598 	const unsigned char *data;
599 	size_t size;
600 	int ret;
601 
602 	test_begin(t_strdup_printf("compression handler %s (large random io, autodetect=%s)",
603 				   handler->name, autodetect ? "yes" : "no"));
604 	randomness = i_malloc(RANDOMNESS_SIZE);
605 	random_fill(randomness, RANDOMNESS_SIZE);
606 
607 	/* write 1 MB of randomness to buffer */
608 	input = i_stream_create_from_data(randomness, RANDOMNESS_SIZE);
609 
610 	temp_output = iostream_temp_create(".temp.", 0);
611 	output = handler->create_ostream(temp_output, i_rand_minmax(1, 6));
612 
613 	switch (o_stream_send_istream(output, input)) {
614 	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
615 	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
616 		test_assert(FALSE);
617 		break;
618 	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
619 	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
620 		i_unreached();
621 	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
622 		test_assert(o_stream_finish(output) == 1);
623 		break;
624 	}
625 	test_assert(output->offset == RANDOMNESS_SIZE);
626 	test_assert(output->stream_errno == 0);
627 	i_stream_unref(&input);
628 
629 	input = iostream_temp_finish(&temp_output, SIZE_MAX);
630 	o_stream_unref(&output);
631 
632 	/* verify that reading the input works */
633 
634 	dec_input = !autodetect ? handler->create_istream(input) :
635 		i_stream_create_decompress(input, 0);
636 
637 	while ((ret = i_stream_read_more(dec_input, &data, &size)) > 0) {
638 		test_assert(memcmp(data, randomness + dec_input->v_offset, size) == 0);
639 		i_stream_skip(dec_input, size);
640 	}
641 	test_assert(ret == -1);
642 	test_assert(dec_input->v_offset == RANDOMNESS_SIZE);
643 	test_assert(dec_input->stream_errno == 0);
644 
645 	i_stream_unref(&dec_input);
646 	i_stream_unref(&input);
647 	i_free(randomness);
648 	test_end();
649 }
650 
651 static void
test_compression_handler_errors(const struct compression_handler * handler,bool autodetect)652 test_compression_handler_errors(const struct compression_handler *handler,
653 				bool autodetect)
654 {
655 	test_begin(t_strdup_printf("compression handler %s (errors, autodetect=%s)",
656 				   handler->name, autodetect ? "yes" : "no"));
657 
658 	/* test that zero stream reading errors out */
659 	struct istream *is = test_istream_create("");
660 	struct istream *input =
661 		!autodetect ? handler->create_istream(is) :
662 		i_stream_create_decompress(is, 0);
663 	i_stream_unref(&is);
664 	test_assert(i_stream_read(input) == -1 && input->eof);
665 	i_stream_unref(&input);
666 
667 	/* test that garbage isn't considered valid */
668 	is = test_istream_create("dedededededededededededededede"
669 				 "dedededeededdedededededededede"
670 				 "dedededededededededededededede");
671 	is->blocking = TRUE;
672 	input = !autodetect ? handler->create_istream(is) :
673 		i_stream_create_decompress(is, 0);
674 	i_stream_unref(&is);
675 	test_assert(i_stream_read(input) == -1 && input->eof);
676 	i_stream_unref(&input);
677 
678 	/* test that truncated data is not considered valid */
679 	buffer_t *odata = buffer_create_dynamic(pool_datastack_create(), 65535);
680 	unsigned char buf[IO_BLOCK_SIZE];
681 	struct ostream *os = test_ostream_create(odata);
682 	struct ostream *output = handler->create_ostream(os, 1);
683 	o_stream_unref(&os);
684 
685 	for (unsigned int i = 0; i < 10; i++) {
686 		random_fill(buf, sizeof(buf));
687 		test_assert(o_stream_send(output, buf, sizeof(buf)) == sizeof(buf));
688 	}
689 
690 	test_assert(o_stream_finish(output) == 1);
691 	o_stream_unref(&output);
692 
693 	/* truncate buffer */
694 	is = test_istream_create_data(odata->data, odata->used - sizeof(buf)*2 - 1);
695 	input = !autodetect ? handler->create_istream(is) :
696 		i_stream_create_decompress(is, 0);
697 	i_stream_unref(&is);
698 
699 	const unsigned char *data ATTR_UNUSED;
700 	size_t size;
701 	while (i_stream_read_more(input, &data, &size) > 0)
702 		i_stream_skip(input, size);
703 
704 	test_assert(input->stream_errno == EPIPE);
705 	i_stream_unref(&input);
706 
707 	/* Cannot do the next check if we don't know if it's compressed. */
708 	if (handler->is_compressed != NULL) {
709 		/* test incrementally reading up to 32 bytes of plaintext data
710 		   that should not match any handlers' header */
711 		for (size_t i = 0; i < 32; i++) {
712 			is = test_istream_create_data("dededededededededededededededede", i);
713 			input = !autodetect ? handler->create_istream(is) :
714 				i_stream_create_decompress(is, 0);
715 			i_stream_unref(&is);
716 			while (i_stream_read_more(input, &data, &size) >= 0) {
717 				test_assert_idx(size == 0, i);
718 				i_stream_skip(input, size);
719 			}
720 			test_assert_idx(input->stream_errno == EINVAL, i);
721 			i_stream_unref(&input);
722 		}
723 	}
724 
725 	test_end();
726 }
727 
test_compression_int(bool autodetect)728 static void test_compression_int(bool autodetect)
729 {
730 	unsigned int i;
731 
732 	for (i = 0; compression_handlers[i].name != NULL; i++) {
733 		if (compression_handlers[i].create_istream != NULL &&
734 		    compression_handlers[i].create_ostream != NULL &&
735 		    (!autodetect ||
736 		     compression_handlers[i].is_compressed != NULL)) T_BEGIN {
737 			if (compression_handlers[i].is_compressed != NULL &&
738 			    !autodetect)
739 				test_compression_handler_detect(&compression_handlers[i]);
740 			test_compression_handler_short(&compression_handlers[i], autodetect);
741 			test_compression_handler_empty(&compression_handlers[i], autodetect);
742 			test_compression_handler(&compression_handlers[i], autodetect);
743 			test_compression_handler_seek(&compression_handlers[i], autodetect);
744 			test_compression_handler_reset(&compression_handlers[i], autodetect);
745 			test_compression_handler_partial_parent_write(&compression_handlers[i], autodetect);
746 			test_compression_handler_random_io(&compression_handlers[i], autodetect);
747 			test_compression_handler_large_random_io(&compression_handlers[i], autodetect);
748 			test_compression_handler_errors(&compression_handlers[i], autodetect);
749 		} T_END;
750 	}
751 }
752 
test_compression(void)753 static void test_compression(void)
754 {
755 	test_compression_int(FALSE);
756 	test_compression_int(TRUE);
757 }
758 
test_istream_decompression_try(void)759 static void test_istream_decompression_try(void)
760 {
761 	const char *tests[] = {
762 		"",
763 		"1",
764 		"12",
765 		"12345678901234567890123456789012345678901234567890",
766 	};
767 	struct istream *is, *input;
768 	const unsigned char *data;
769 	size_t size;
770 
771 	test_begin("istream-decompression try");
772 
773 	for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
774 		size_t test_len = strlen(tests[i]);
775 		is = i_stream_create_from_data(tests[i], test_len);
776 		input = i_stream_create_decompress(is, ISTREAM_DECOMPRESS_FLAG_TRY);
777 		i_stream_unref(&is);
778 
779 		ssize_t ret = i_stream_read(input);
780 		test_assert_idx((test_len == 0 && ret == -1) ||
781 				(test_len > 0 && ret == (ssize_t)test_len), i);
782 		data = i_stream_get_data(input, &size);
783 		test_assert_idx(size == test_len &&
784 				memcmp(data, tests[i], size) == 0, i);
785 
786 		i_stream_skip(input, size);
787 		test_assert_idx(i_stream_read(input) == -1, i);
788 		test_assert_idx(input->stream_errno == 0, i);
789 		i_stream_unref(&input);
790 	}
791 	test_end();
792 }
793 
test_gz(const char * str1,const char * str2,bool autodetect)794 static void test_gz(const char *str1, const char *str2, bool autodetect)
795 {
796 	const struct compression_handler *gz;
797 	struct ostream *buf_output, *output;
798 	struct istream *test_input, *input;
799 	buffer_t *buf = t_buffer_create(512);
800 
801 	if (compression_lookup_handler("gz", &gz) <= 0 )
802 		return; /* not compiled in or unkown*/
803 
804 	/* write concated output */
805 	buf_output = o_stream_create_buffer(buf);
806 	o_stream_set_finish_via_child(buf_output, FALSE);
807 
808 	output = gz->create_ostream(buf_output, 6);
809 	o_stream_nsend_str(output, str1);
810 	test_assert(o_stream_finish(output) > 0);
811 	o_stream_destroy(&output);
812 
813 	if (str2[0] != '\0') {
814 		output = gz->create_ostream(buf_output, 6);
815 		o_stream_nsend_str(output, "world");
816 		test_assert(o_stream_finish(output) > 0);
817 		o_stream_destroy(&output);
818 	}
819 
820 	o_stream_destroy(&buf_output);
821 
822 	/* read concated input */
823 	const unsigned char *data;
824 	size_t size;
825 	test_input = test_istream_create_data(buf->data, buf->used);
826 	test_istream_set_allow_eof(test_input, FALSE);
827 	input = !autodetect ? gz->create_istream(test_input) :
828 		i_stream_create_decompress(test_input, 0);
829 	for (size_t i = 0; i <= buf->used; i++) {
830 		test_istream_set_size(test_input, i);
831 		test_assert(i_stream_read(input) >= 0);
832 	}
833 	test_istream_set_allow_eof(test_input, TRUE);
834 	test_assert(i_stream_read(input) == -1);
835 	test_assert(input->stream_errno == 0);
836 
837 	data = i_stream_get_data(input, &size);
838 	test_assert(size == strlen(str1)+strlen(str2) &&
839 		    memcmp(data, str1, strlen(str1)) == 0 &&
840 		    memcmp(data+strlen(str1), str2, strlen(str2)) == 0);
841 	i_stream_unref(&input);
842 	i_stream_unref(&test_input);
843 }
844 
test_gz_concat(void)845 static void test_gz_concat(void)
846 {
847 	test_begin("gz concat (autodetect=no)");
848 	test_gz("hello", "world", FALSE);
849 	test_end();
850 
851 	test_begin("gz concat (autodetect=yes)");
852 	test_gz("hello", "world", TRUE);
853 	test_end();
854 }
855 
test_gz_no_concat(void)856 static void test_gz_no_concat(void)
857 {
858 	test_begin("gz no concat (autodetect=no)");
859 	test_gz("hello", "", FALSE);
860 	test_end();
861 
862 	test_begin("gz no concat (autodetect=yes)");
863 	test_gz("hello", "", TRUE);
864 	test_end();
865 }
866 
test_gz_header_int(bool autodetect)867 static void test_gz_header_int(bool autodetect)
868 {
869 	const struct compression_handler *gz;
870 	const char *input_strings[] = {
871 		"\x1F\x8B",
872 		"\x1F\x8B\x01\x02"/* GZ_FLAG_FHCRC */"\xFF\xFF\x01\x01\x01\x01",
873 		"\x1F\x8B\x01\x04"/* GZ_FLAG_FEXTRA */"\xFF\xFF\x01\x01\x01\x01",
874 		"\x1F\x8B\x01\x08"/* GZ_FLAG_FNAME */"\x01\x01\x01\x01\x01\x01",
875 		"\x1F\x8B\x01\x10"/* GZ_FLAG_FCOMMENT */"\x01\x01\x01\x01\x01\x01",
876 		"\x1F\x8B\x01\x0C"/* GZ_FLAG_FEXTRA | GZ_FLAG_FNAME */"\xFF\xFF\x01\x01\x01\x01",
877 	};
878 	struct istream *file_input, *input;
879 	if (compression_lookup_handler("gz", &gz) <= 0 )
880 		return; /* not compiled in or unkown*/
881 
882 	test_begin(t_strdup_printf(
883 		"gz header (autodetect=%s)", autodetect ? "yes" : "no"));
884 	for (unsigned int i = 0; i < N_ELEMENTS(input_strings); i++) {
885 		file_input = test_istream_create_data(input_strings[i],
886 						      strlen(input_strings[i]));
887 		file_input->blocking = TRUE;
888 		input = !autodetect ? gz->create_istream(file_input) :
889 			i_stream_create_decompress(file_input, 0);
890 		test_assert_idx(i_stream_read(input) == -1, i);
891 		test_assert_idx(input->stream_errno == EINVAL, i);
892 		i_stream_unref(&input);
893 		i_stream_unref(&file_input);
894 	}
895 	test_end();
896 }
897 
test_gz_header(void)898 static void test_gz_header(void)
899 {
900 	test_gz_header_int(FALSE);
901 	test_gz_header_int(TRUE);
902 }
903 
test_gz_large_header_int(bool autodetect)904 static void test_gz_large_header_int(bool autodetect)
905 {
906 	const struct compression_handler *gz;
907 	static const unsigned char gz_input[] = {
908 		0x1f, 0x8b, 0x08, 0x08,
909 		'a','a','a','a','a','a','a','a','a','a','a',
910 		0
911 	};
912 	struct istream *file_input, *input;
913 	size_t i;
914 
915 	if (compression_lookup_handler("gz", &gz) <= 0 )
916 		return; /* not compiled in or unkown*/
917 
918 	test_begin(t_strdup_printf(
919 		"gz large header (autodetect=%s)", autodetect ? "yes" : "no"));
920 
921 	/* max buffer size smaller than gz header */
922 	for (i = 1; i < sizeof(gz_input); i++) {
923 		file_input = test_istream_create_data(gz_input, sizeof(gz_input));
924 		test_istream_set_size(file_input, i);
925 		test_istream_set_max_buffer_size(file_input, i);
926 
927 		input = !autodetect ? gz->create_istream(file_input) :
928 			i_stream_create_decompress(file_input, 0);
929 		test_assert_idx(i_stream_read(input) == 0, i);
930 		test_assert_idx(i_stream_read(input) == -1 &&
931 				input->stream_errno == EINVAL, i);
932 		i_stream_unref(&input);
933 		i_stream_unref(&file_input);
934 	}
935 
936 	/* max buffer size is exactly the gz header */
937 	file_input = test_istream_create_data(gz_input, sizeof(gz_input));
938 	input = !autodetect ? gz->create_istream(file_input) :
939 		i_stream_create_decompress(file_input, 0);
940 	test_istream_set_size(file_input, i);
941 	test_istream_set_allow_eof(file_input, FALSE);
942 	test_istream_set_max_buffer_size(file_input, i);
943 	test_assert(i_stream_read(input) == 0);
944 	i_stream_unref(&input);
945 	i_stream_unref(&file_input);
946 
947 	test_end();
948 }
949 
test_gz_large_header(void)950 static void test_gz_large_header(void)
951 {
952 	test_gz_large_header_int(FALSE);
953 	test_gz_large_header_int(TRUE);
954 }
955 
test_lz4_small_header(void)956 static void test_lz4_small_header(void)
957 {
958 	const struct compression_handler *lz4;
959 	struct iostream_lz4_header lz4_input;
960 	struct istream *file_input, *input;
961 
962 	if (compression_lookup_handler("lz4", &lz4) <= 0)
963 		return; /* not compiled in or unkown */
964 
965 	test_begin("lz4 small header");
966 
967 	memcpy(lz4_input.magic, IOSTREAM_LZ4_MAGIC, IOSTREAM_LZ4_MAGIC_LEN);
968 	lz4_input.max_uncompressed_chunk_size[0] = 0;
969 	lz4_input.max_uncompressed_chunk_size[1] = 1;
970 	lz4_input.max_uncompressed_chunk_size[2] = 0;
971 	lz4_input.max_uncompressed_chunk_size[3] = 0;
972 
973 	/* truncated header */
974 	file_input = i_stream_create_from_data(&lz4_input, sizeof(lz4_input)-1);
975 	input = lz4->create_istream(file_input);
976 	test_assert(i_stream_read(input) == -1 &&
977 		    input->stream_errno == EINVAL);
978 	i_stream_unref(&input);
979 	i_stream_unref(&file_input);
980 
981 	/* partial initial header read */
982 	file_input = test_istream_create_data(&lz4_input, sizeof(lz4_input));
983 	file_input->blocking = TRUE;
984 
985 	test_istream_set_max_buffer_size(file_input, sizeof(lz4_input)-1);
986 	(void)i_stream_read(file_input);
987 	test_istream_set_max_buffer_size(file_input, sizeof(lz4_input));
988 
989 	input = lz4->create_istream(file_input);
990 	test_assert(i_stream_read(input) == -1 &&
991 		    input->stream_errno == 0);
992 	i_stream_unref(&input);
993 	i_stream_unref(&file_input);
994 
995 	test_end();
996 }
997 
test_uncompress_file(const char * path)998 static void test_uncompress_file(const char *path)
999 {
1000 	const struct compression_handler *handler;
1001 	struct istream *input, *file_input;
1002 	const unsigned char *data;
1003 	size_t size;
1004 	int ret;
1005 
1006 	ret = compression_lookup_handler_from_ext(path, &handler);
1007 	if (ret < 0)
1008 		i_fatal("Can't detect compression algorithm from path %s", path);
1009 	else if (ret == 0)
1010 		i_fatal("Support not compiled in for %s", handler->name);
1011 
1012 	file_input = i_stream_create_file(path, IO_BLOCK_SIZE);
1013 	input = handler->create_istream(file_input);
1014 	while (i_stream_read_more(input, &data, &size) > 0) {
1015 		if (write(STDOUT_FILENO, data, size) < 0)
1016 			break;
1017 		i_stream_skip(input, size);
1018 	}
1019 	i_stream_destroy(&input);
1020 }
1021 
test_compress_file(const char * in_path,const char * out_path)1022 static void test_compress_file(const char *in_path, const char *out_path)
1023 {
1024 	const struct compression_handler *handler;
1025 	struct istream *input, *file_input;
1026 	struct ostream *output, *file_output;
1027 	int fd_in, fd_out;
1028 	struct sha1_ctxt sha1;
1029 	unsigned char output_sha1[SHA1_RESULTLEN], input_sha1[SHA1_RESULTLEN];
1030 	const unsigned char *data;
1031 	size_t size;
1032 	int ret;
1033 
1034 	ret = compression_lookup_handler_from_ext(out_path, &handler);
1035 	if (ret < 0)
1036 		i_fatal("Can't detect compression algorithm from path %s", out_path);
1037 	if (ret == 0)
1038 		i_fatal("Support not compiled in for %s", handler->name);
1039 
1040 	/* write the compressed output file */
1041 	fd_in = open(in_path, O_RDONLY);
1042 	if (fd_in == -1)
1043 		i_fatal("open(%s) failed: %m", in_path);
1044 	fd_out = open(out_path, O_TRUNC | O_CREAT | O_RDWR, 0600);
1045 	if (fd_out == -1)
1046 		i_fatal("creat(%s) failed: %m", out_path);
1047 
1048 	sha1_init(&sha1);
1049 	file_output = o_stream_create_fd_file(fd_out, 0, FALSE);
1050 	output = handler->create_ostream(file_output, 1);
1051 	input = i_stream_create_fd_autoclose(&fd_in, IO_BLOCK_SIZE);
1052 	while (i_stream_read_more(input, &data, &size) > 0) {
1053 		sha1_loop(&sha1, data, size);
1054 		o_stream_nsend(output, data, size);
1055 		i_stream_skip(input, size);
1056 	}
1057 	if (o_stream_finish(output) < 0) {
1058 		i_fatal("write(%s) failed: %s",
1059 			out_path, o_stream_get_error(output));
1060 	}
1061 	i_stream_destroy(&input);
1062 	o_stream_destroy(&output);
1063 	o_stream_destroy(&file_output);
1064 	sha1_result(&sha1, output_sha1);
1065 
1066 	/* verify that we can read the compressed file */
1067 	sha1_init(&sha1);
1068 	file_input = i_stream_create_fd(fd_out, IO_BLOCK_SIZE);
1069 	input = handler->create_istream(file_input);
1070 	while (i_stream_read_more(input, &data, &size) > 0) {
1071 		sha1_loop(&sha1, data, size);
1072 		i_stream_skip(input, size);
1073 	}
1074 	i_stream_destroy(&input);
1075 	i_stream_destroy(&file_input);
1076 	sha1_result(&sha1, input_sha1);
1077 
1078 	if (memcmp(input_sha1, output_sha1, sizeof(input_sha1)) != 0)
1079 		i_fatal("Decompression couldn't get the original input");
1080 	i_close_fd(&fd_out);
1081 }
1082 
test_compression_ext(void)1083 static void test_compression_ext(void)
1084 {
1085 	const struct compression_handler *handler;
1086 	test_begin("compression handler by extension");
1087 
1088 	for (unsigned int i = 0; compression_handlers[i].name != NULL; i++) {
1089 		if (compression_handlers[i].ext != NULL) {
1090 			const char *path =
1091 				t_strconcat("file", compression_handlers[i].ext, NULL);
1092 			int ret = compression_lookup_handler_from_ext(path, &handler);
1093 			test_assert(ret == 0 || handler == &compression_handlers[i]);
1094 		}
1095 	}
1096 
1097 	test_end();
1098 }
1099 
main(int argc,char * argv[])1100 int main(int argc, char *argv[])
1101 {
1102 	static void (*const test_functions[])(void) = {
1103 		test_compression,
1104 		test_istream_decompression_try,
1105 		test_gz_concat,
1106 		test_gz_no_concat,
1107 		test_gz_header,
1108 		test_gz_large_header,
1109 		test_lz4_small_header,
1110 		test_compression_ext,
1111 		NULL
1112 	};
1113 	if (argc == 2) {
1114 		test_uncompress_file(argv[1]);
1115 		return 0;
1116 	}
1117 	if (argc == 3) {
1118 		test_compress_file(argv[1], argv[2]);
1119 		return 0;
1120 	}
1121 	return test_run(test_functions);
1122 }
1123