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