1 /******************************************************************************* 2 3 Pending completele removal after ocean utilities using it are adjusted 4 5 Copyright: 6 Copyright (C) 2007 Daniel Keep. 7 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 8 All rights reserved. 9 10 License: 11 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 12 See LICENSE_TANGO.txt for details. 13 14 Bear in mind this module provides bindings to an external library that 15 has its own license, which might be more restrictive. Please check the 16 external library license to see which conditions apply for linking. 17 18 Version: 19 Feb 08: Added support for different stream encodings, removed 20 old "window bits" ctors.$(BR) 21 Dec 07: Added support for "window bits", needed for Zip support.$(BR) 22 Jul 07: Initial release. 23 24 Authors: Daniel Keep 25 26 *******************************************************************************/ 27 28 module ocean.io.stream.Zlib_internal; 29 30 // package(ocean): 31 32 import ocean.meta.types.Qualifiers; 33 34 import ocean.core.Verify; 35 36 import ocean.util.compress.c.zlib; 37 38 import ocean.core.ExceptionDefinitions : IOException; 39 40 import ocean.io.device.Conduit : InputFilter, OutputFilter; 41 42 import ocean.io.model.IConduit : InputStream, OutputStream, IConduit; 43 44 import ocean.text.convert.Integer_tango : toString; 45 import ocean.text.util.StringC; 46 47 version (unittest) import ocean.core.Test; 48 49 /* This constant controls the size of the input/output buffers we use 50 * internally. This should be a fairly sane value (it's suggested by the zlib 51 * documentation), that should only need changing for memory-constrained 52 * platforms/use cases. 53 * 54 * An alternative would be to make the chunk size a template parameter to the 55 * filters themselves, but Tango already has more than enough template 56 * parameters getting in the way :) 57 */ 58 59 private enum { CHUNKSIZE = 256 * 1024 }; 60 61 /* This constant specifies the default windowBits value. This is taken from 62 * documentation in zlib.h. It shouldn't break anything if zlib changes to 63 * a different default. 64 */ 65 66 private enum { WINDOWBITS_DEFAULT = 15 }; 67 68 /******************************************************************************* 69 70 This input filter can be used to perform decompression of zlib streams. 71 72 *******************************************************************************/ 73 74 class ZlibInput : InputFilter 75 { 76 /*************************************************************************** 77 78 This enumeration allows you to specify the encoding of the compressed 79 stream. 80 81 ***************************************************************************/ 82 83 enum Encoding : int 84 { 85 /** 86 * The code should attempt to automatically determine what the encoding 87 * of the stream should be. Note that this cannot detect the case 88 * where the stream was compressed with no encoding. 89 */ 90 Guess, 91 /** 92 * Stream has zlib encoding. 93 */ 94 Zlib, 95 /** 96 * Stream has gzip encoding. 97 */ 98 Gzip, 99 /** 100 * Stream has no encoding. 101 */ 102 None 103 } 104 105 private 106 { 107 /* Used to make sure we don't try to perform operations on a dead 108 * stream. */ 109 bool zs_valid = false; 110 111 z_stream zs; 112 ubyte[] in_chunk; 113 } 114 115 /*************************************************************************** 116 117 Constructs a new zlib decompression filter. You need to pass in the 118 stream that the decompression filter will read from. If you are using 119 this filter with a conduit, the idiom to use is: 120 121 --- 122 auto input = new ZlibInput(myConduit.input); 123 input.read(myContent); 124 --- 125 126 The optional windowBits parameter is the base two logarithm of the 127 window size, and should be in the range 8-15, defaulting to 15 if not 128 specified. Additionally, the windowBits parameter may be negative to 129 indicate that zlib should omit the standard zlib header and trailer, 130 with the window size being -windowBits. 131 132 Params: 133 stream = Compressed input stream. 134 135 encoding = 136 Stream encoding. Defaults to Encoding.Guess, which 137 should be sufficient unless the stream was compressed with 138 no encoding; in this case, you must manually specify 139 Encoding.None. 140 141 windowBits = 142 The base two logarithm of the window size, and should be in the 143 range 8-15, defaulting to 15 if not specified. 144 145 ***************************************************************************/ 146 147 this(InputStream stream, Encoding encoding, 148 int windowBits = WINDOWBITS_DEFAULT) 149 { 150 init(stream, encoding, windowBits); 151 scope(failure) kill_zs(); 152 153 super(stream); 154 in_chunk = new ubyte[CHUNKSIZE]; 155 } 156 157 /// ditto 158 this(InputStream stream) 159 { 160 // DRK 2009-02-26 161 // Removed unique implementation in favour of passing on to another 162 // constructor. The specific implementation was because the default 163 // value of windowBits is documented in zlib.h, but not actually 164 // exposed. Using inflateInit over inflateInit2 ensured we would 165 // never get it wrong. That said, the default value of 15 is REALLY 166 // unlikely to change: values below that aren't terribly useful, and 167 // values higher than 15 are already used for other purposes. 168 // Also, this leads to less code which is always good. :D 169 this(stream, Encoding.init); 170 } 171 172 /* 173 * This method performs initialisation for the stream. Note that this may 174 * be called more than once for an instance, provided the instance is 175 * either new or as part of a call to reset. 176 */ 177 private void init(InputStream stream, Encoding encoding, int windowBits) 178 { 179 /* 180 * Here's how windowBits works, according to zlib.h: 181 * 182 * 8 .. 15 183 * zlib encoding. 184 * 185 * (8 .. 15) + 16 186 * gzip encoding. 187 * 188 * (8 .. 15) + 32 189 * auto-detect encoding. 190 * 191 * (8 .. 15) * -1 192 * raw/no encoding. 193 * 194 * Since we're going to be playing with the value, we DO care whether 195 * windowBits is in the expected range, so we'll check it. 196 */ 197 if( !( 8 <= windowBits && windowBits <= 15 ) ) 198 { 199 // No compression for you! 200 throw new ZlibException(idup("invalid windowBits argument" 201 ~ .toString(windowBits))); 202 } 203 204 switch( encoding ) 205 { 206 case Encoding.Zlib: 207 // no-op 208 break; 209 210 case Encoding.Gzip: 211 windowBits += 16; 212 break; 213 214 case Encoding.Guess: 215 windowBits += 32; 216 break; 217 218 case Encoding.None: 219 windowBits *= -1; 220 break; 221 222 default: 223 assert (false); 224 } 225 226 // Allocate inflate state 227 with( zs ) 228 { 229 zalloc = null; 230 zfree = null; 231 opaque = null; 232 avail_in = 0; 233 next_in = null; 234 } 235 236 auto ret = inflateInit2(&zs, windowBits); 237 if( ret != Z_OK ) 238 throw new ZlibException(ret); 239 240 zs_valid = true; 241 242 // Note that this is redundant when init is called from the ctor, but 243 // it is NOT REDUNDANT when called from reset. source is declared in 244 // InputFilter. 245 // 246 // This code is a wee bit brittle, since if the ctor of InputFilter 247 // changes, this code might break in subtle, hard to find ways. 248 // 249 // See ticket #1837 250 this.source = stream; 251 } 252 253 ~this() 254 { 255 if( zs_valid ) 256 kill_zs(); 257 } 258 259 /*************************************************************************** 260 261 Resets and re-initialises this instance. 262 263 If you are creating compression streams inside a loop, you may wish to 264 use this method to re-use a single instance. This prevents the 265 potentially costly re-allocation of internal buffers. 266 267 The stream must have already been closed before calling reset. 268 269 ***************************************************************************/ 270 271 void reset(InputStream stream, Encoding encoding, 272 int windowBits = WINDOWBITS_DEFAULT) 273 { 274 // If the stream is still valid, bail. 275 if( zs_valid ) 276 throw new ZlibStillOpenException; 277 278 init(stream, encoding, windowBits); 279 } 280 281 /// ditto 282 283 void reset(InputStream stream) 284 { 285 reset(stream, Encoding.init); 286 } 287 288 /*************************************************************************** 289 290 Decompresses data from the underlying conduit into a target array. 291 292 Returns the number of bytes stored into dst, which may be less than 293 requested. 294 295 ***************************************************************************/ 296 297 override size_t read(void[] dst) 298 { 299 if( !zs_valid ) 300 return IConduit.Eof; 301 302 // Check to see if we've run out of input data. If we have, get some 303 // more. 304 if( zs.avail_in == 0 ) 305 { 306 auto len = source.read(in_chunk); 307 if( len == IConduit.Eof ) 308 return IConduit.Eof; 309 310 zs.avail_in = cast(uint) len; 311 zs.next_in = in_chunk.ptr; 312 } 313 314 // We'll tell zlib to inflate straight into the target array. 315 zs.avail_out = cast(uint) dst.length; 316 zs.next_out = cast(ubyte*)dst.ptr; 317 auto ret = inflate(&zs, Z_NO_FLUSH); 318 319 switch( ret ) 320 { 321 case Z_NEED_DICT: 322 // Whilst not technically an error, this should never happen 323 // for general-use code, so treat it as an error. 324 case Z_DATA_ERROR: 325 case Z_MEM_ERROR: 326 kill_zs(); 327 throw new ZlibException(ret); 328 329 case Z_STREAM_END: 330 // zlib stream is finished; kill the stream so we don't try to 331 // read from it again. 332 kill_zs(); 333 break; 334 335 default: 336 } 337 338 return dst.length - zs.avail_out; 339 } 340 341 /*************************************************************************** 342 343 Closes the compression stream. 344 345 ***************************************************************************/ 346 347 override void close() 348 { 349 // Kill the stream. Don't deallocate the buffer since the user may 350 // yet reset the stream. 351 if( zs_valid ) 352 kill_zs(); 353 354 super.close(); 355 } 356 357 // Disable seeking 358 override long seek(long offset, Anchor anchor = Anchor.Begin) 359 { 360 throw new IOException("ZlibInput does not support seek requests"); 361 } 362 363 // This function kills the stream: it deallocates the internal state, and 364 // unsets the zs_valid flag. 365 private void kill_zs() 366 { 367 check_valid(); 368 369 inflateEnd(&zs); 370 zs_valid = false; 371 } 372 373 // Asserts that the stream is still valid and usable (except that this 374 // check doesn't get elided with -release). 375 private void check_valid() 376 { 377 if( !zs_valid ) 378 throw new ZlibClosedException; 379 } 380 } 381 382 /******************************************************************************* 383 384 This output filter can be used to perform compression of data into a zlib 385 stream. 386 387 *******************************************************************************/ 388 389 class ZlibOutput : OutputFilter 390 { 391 /*************************************************************************** 392 393 This enumeration represents several pre-defined compression levels. 394 395 Any integer between -1 and 9 inclusive may be used as a level, 396 although the symbols in this enumeration should suffice for most 397 use-cases. 398 399 ***************************************************************************/ 400 401 enum Level : int 402 { 403 /** 404 * Default compression level. This is selected for a good compromise 405 * between speed and compression, and the exact compression level is 406 * determined by the underlying zlib library. Should be roughly 407 * equivalent to compression level 6. 408 */ 409 Normal = -1, 410 /** 411 * Do not perform compression. This will cause the stream to expand 412 * slightly to accommodate stream metadata. 413 */ 414 None = 0, 415 /** 416 * Minimal compression; the fastest level which performs at least 417 * some compression. 418 */ 419 Fast = 1, 420 /** 421 * Maximal compression. 422 */ 423 Best = 9 424 } 425 426 /*************************************************************************** 427 428 This enumeration allows you to specify what the encoding of the 429 compressed stream should be. 430 431 ***************************************************************************/ 432 433 enum Encoding : int 434 { 435 /** 436 * Stream should use zlib encoding. 437 */ 438 Zlib, 439 /** 440 * Stream should use gzip encoding. 441 */ 442 Gzip, 443 /** 444 * Stream should use no encoding. 445 */ 446 None 447 } 448 449 private 450 { 451 bool zs_valid = false; 452 z_stream zs; 453 ubyte[] out_chunk; 454 size_t _written = 0; 455 } 456 457 /*************************************************************************** 458 459 Constructs a new zlib compression filter. You need to pass in the 460 stream that the compression filter will write to. If you are using 461 this filter with a conduit, the idiom to use is: 462 463 --- 464 auto output = new ZlibOutput(myConduit.output); 465 output.write(myContent); 466 --- 467 468 The optional windowBits parameter is the base two logarithm of the 469 window size, and should be in the range 8-15, defaulting to 15 if not 470 specified. Additionally, the windowBits parameter may be negative to 471 indicate that zlib should omit the standard zlib header and trailer, 472 with the window size being -windowBits. 473 474 ***************************************************************************/ 475 476 this(OutputStream stream, Level level, Encoding encoding, 477 int windowBits = WINDOWBITS_DEFAULT) 478 { 479 init(stream, level, encoding, windowBits); 480 scope(failure) kill_zs(); 481 482 super(stream); 483 out_chunk = new ubyte[CHUNKSIZE]; 484 } 485 486 /// ditto 487 this(OutputStream stream, Level level = Level.Normal) 488 { 489 // DRK 2009-02-26 490 // Removed unique implementation in favour of passing on to another 491 // constructor. See ZlibInput.this(InputStream). 492 this(stream, level, Encoding.init); 493 } 494 495 /* 496 * This method performs initialisation for the stream. Note that this may 497 * be called more than once for an instance, provided the instance is 498 * either new or as part of a call to reset. 499 */ 500 private void init(OutputStream stream, Level level, Encoding encoding, 501 int windowBits) 502 { 503 /* 504 * Here's how windowBits works, according to zlib.h: 505 * 506 * 8 .. 15 507 * zlib encoding. 508 * 509 * (8 .. 15) + 16 510 * gzip encoding. 511 * 512 * (8 .. 15) + 32 513 * auto-detect encoding. 514 * 515 * (8 .. 15) * -1 516 * raw/no encoding. 517 * 518 * Since we're going to be playing with the value, we DO care whether 519 * windowBits is in the expected range, so we'll check it. 520 * 521 * Also, note that OUR Encoding enum doesn't contain the 'Guess' 522 * member. I'm still waiting on ocean.io.psychic... 523 */ 524 if( !( 8 <= windowBits && windowBits <= 15 ) ) 525 { 526 // No compression for you! 527 throw new ZlibException(idup("invalid windowBits argument" 528 ~ .toString(windowBits))); 529 } 530 531 switch( encoding ) 532 { 533 case Encoding.Zlib: 534 // no-op 535 break; 536 537 case Encoding.Gzip: 538 windowBits += 16; 539 break; 540 541 case Encoding.None: 542 windowBits *= -1; 543 break; 544 545 default: 546 assert (false); 547 } 548 549 // Allocate deflate state 550 with( zs ) 551 { 552 zalloc = null; 553 zfree = null; 554 opaque = null; 555 } 556 557 auto ret = deflateInit2(&zs, level, Z_DEFLATED, windowBits, 8, 558 Z_DEFAULT_STRATEGY); 559 if( ret != Z_OK ) 560 throw new ZlibException(ret); 561 562 zs_valid = true; 563 564 // This is NOT REDUNDANT. See ZlibInput.init. 565 this.sink = stream; 566 } 567 568 ~this() 569 { 570 if( zs_valid ) 571 kill_zs(); 572 } 573 574 /*************************************************************************** 575 576 Resets and re-initialises this instance. 577 578 If you are creating compression streams inside a loop, you may wish to 579 use this method to re-use a single instance. This prevents the 580 potentially costly re-allocation of internal buffers. 581 582 The stream must have already been closed or committed before calling 583 reset. 584 585 ***************************************************************************/ 586 587 void reset(OutputStream stream, Level level, Encoding encoding, 588 int windowBits = WINDOWBITS_DEFAULT) 589 { 590 // If the stream is still valid, bail. 591 if( zs_valid ) 592 throw new ZlibStillOpenException; 593 594 init(stream, level, encoding, windowBits); 595 } 596 597 /// ditto 598 void reset(OutputStream stream, Level level = Level.Normal) 599 { 600 reset(stream, level, Encoding.init); 601 } 602 603 /*************************************************************************** 604 605 Compresses the given data to the underlying conduit. 606 607 Returns the number of bytes from src that were compressed; write 608 should always consume all data provided to it, although it may not be 609 immediately written to the underlying output stream. 610 611 ***************************************************************************/ 612 613 override size_t write(const(void)[] src) 614 { 615 check_valid(); 616 scope(failure) kill_zs(); 617 618 zs.avail_in = cast(uint) src.length; 619 zs.next_in = cast(ubyte*)src.ptr; 620 621 do 622 { 623 zs.avail_out = cast(uint) out_chunk.length; 624 zs.next_out = out_chunk.ptr; 625 626 auto ret = deflate(&zs, Z_NO_FLUSH); 627 if( ret == Z_STREAM_ERROR ) 628 throw new ZlibException(ret); 629 630 // Push the compressed bytes out to the stream, until it's either 631 // written them all, or choked. 632 auto have = out_chunk.length-zs.avail_out; 633 auto out_buffer = out_chunk[0..have]; 634 do 635 { 636 auto w = sink.write(out_buffer); 637 if( w == IConduit.Eof ) 638 return w; 639 640 out_buffer = out_buffer[w..$]; 641 _written += w; 642 } 643 while( out_buffer.length > 0 ); 644 } 645 // Loop while we are still using up the whole output buffer 646 while( zs.avail_out == 0 ); 647 648 verify(zs.avail_in == 0, "failed to compress all provided data"); 649 650 return src.length; 651 } 652 653 /*************************************************************************** 654 655 This read-only property returns the number of compressed bytes that 656 have been written to the underlying stream. Following a call to 657 either close or commit, this will contain the total compressed size of 658 the input data stream. 659 660 ***************************************************************************/ 661 662 size_t written() 663 { 664 return _written; 665 } 666 667 /*************************************************************************** 668 669 Close the compression stream. This will cause any buffered content to 670 be committed to the underlying stream. 671 672 ***************************************************************************/ 673 674 override void close() 675 { 676 // Only commit if the stream is still open. 677 if( zs_valid ) commit; 678 679 super.close; 680 } 681 682 /*************************************************************************** 683 684 Purge any buffered content. Calling this will implicitly end the zlib 685 stream, so it should not be called until you are finished compressing 686 data. Any calls to either write or commit after a compression filter 687 has been committed will throw an exception. 688 689 The only difference between calling this method and calling close is 690 that the underlying stream will not be closed. 691 692 ***************************************************************************/ 693 694 void commit() 695 { 696 check_valid(); 697 scope(failure) kill_zs(); 698 699 zs.avail_in = 0; 700 zs.next_in = null; 701 702 bool finished = false; 703 704 do 705 { 706 zs.avail_out = cast(uint) out_chunk.length; 707 zs.next_out = out_chunk.ptr; 708 709 auto ret = deflate(&zs, Z_FINISH); 710 switch( ret ) 711 { 712 case Z_OK: 713 // Keep going 714 break; 715 716 case Z_STREAM_END: 717 // We're done! 718 finished = true; 719 break; 720 721 default: 722 throw new ZlibException(ret); 723 } 724 725 auto have = out_chunk.length - zs.avail_out; 726 auto out_buffer = out_chunk[0..have]; 727 if( have > 0 ) 728 { 729 do 730 { 731 auto w = sink.write(out_buffer); 732 if( w == IConduit.Eof ) 733 return; 734 735 out_buffer = out_buffer[w..$]; 736 _written += w; 737 } 738 while( out_buffer.length > 0 ); 739 } 740 } 741 while( !finished ); 742 743 kill_zs(); 744 } 745 746 // Disable seeking 747 override long seek(long offset, Anchor anchor = Anchor.Begin) 748 { 749 throw new IOException("ZlibOutput does not support seek requests"); 750 } 751 752 // This function kills the stream: it deallocates the internal state, and 753 // unsets the zs_valid flag. 754 private void kill_zs() 755 { 756 check_valid(); 757 758 deflateEnd(&zs); 759 zs_valid = false; 760 } 761 762 // Asserts that the stream is still valid and usable (except that this 763 // check doesn't get elided with -release). 764 private void check_valid() 765 { 766 if( !zs_valid ) 767 throw new ZlibClosedException; 768 } 769 } 770 771 /******************************************************************************* 772 773 This exception is thrown if you attempt to perform a read, write or flush 774 operation on a closed zlib filter stream. This can occur if the input 775 stream has finished, or an output stream was flushed. 776 777 *******************************************************************************/ 778 779 class ZlibClosedException : IOException 780 { 781 this() 782 { 783 super("cannot operate on closed zlib stream"); 784 } 785 } 786 787 /******************************************************************************* 788 789 This exception is thrown if you attempt to reset a compression stream that 790 is still open. You must either close or commit a stream before it can be 791 reset. 792 793 *******************************************************************************/ 794 795 class ZlibStillOpenException : IOException 796 { 797 this() 798 { 799 super("cannot reset an open zlib stream"); 800 } 801 } 802 803 /******************************************************************************* 804 805 This exception is thrown when an error occurs in the underlying zlib 806 library. Where possible, it will indicate both the name of the error, and 807 any textural message zlib has provided. 808 809 *******************************************************************************/ 810 811 class ZlibException : IOException 812 { 813 /* 814 * Use this if you want to throw an exception that isn't actually 815 * generated by zlib. 816 */ 817 this(istring msg) 818 { 819 super(msg); 820 } 821 822 /* 823 * code is the error code returned by zlib. The exception message will 824 * be the name of the error code. 825 */ 826 this(int code) 827 { 828 super(codeName(code)); 829 } 830 831 /* 832 * As above, except that it appends msg as well. 833 */ 834 this(int code, char* msg) 835 { 836 istring m = codeName(code) ~ ": "; 837 m ~= StringC.toDString(msg); 838 super(m); 839 } 840 841 protected istring codeName(int code) 842 { 843 istring name; 844 845 switch( code ) 846 { 847 case Z_OK: name = "Z_OK"; break; 848 case Z_STREAM_END: name = "Z_STREAM_END"; break; 849 case Z_NEED_DICT: name = "Z_NEED_DICT"; break; 850 case Z_ERRNO: name = "Z_ERRNO"; break; 851 case Z_STREAM_ERROR: name = "Z_STREAM_ERROR"; break; 852 case Z_DATA_ERROR: name = "Z_DATA_ERROR"; break; 853 case Z_MEM_ERROR: name = "Z_MEM_ERROR"; break; 854 case Z_BUF_ERROR: name = "Z_BUF_ERROR"; break; 855 case Z_VERSION_ERROR: name = "Z_VERSION_ERROR"; break; 856 default: name = "Z_UNKNOWN"; 857 } 858 859 return name; 860 } 861 } 862 863 /* ***************************************************************************** 864 865 This section contains a simple unit test for this module. It is hidden 866 behind a version statement because it introduces additional dependencies. 867 868 ***************************************************************************** */ 869 870 version (unittest) 871 { 872 import ocean.io.device.Array : Array; 873 import ocean.core.Test; 874 875 void check_array(istring FILE=__FILE__, int LINE=__LINE__)( 876 const(ubyte)[] as, ubyte[] bs, lazy istring msg) 877 { 878 test( as.length == bs.length, 879 FILE ~":"~ toString(LINE) ~ ": " ~ msg() 880 ~ "array lengths differ (" ~ toString(as.length) 881 ~ " vs " ~ toString(bs.length) ~ ")" ); 882 883 foreach( i, a ; as ) 884 { 885 auto b = bs[i]; 886 887 test( a == b, 888 FILE ~":"~ toString(LINE) ~ ": " ~ msg() 889 ~ "arrays differ at " ~ toString(i) 890 ~ " (" ~ toString(cast(int) a) 891 ~ " vs " ~ toString(cast(int) b) ~ ")" ); 892 } 893 } 894 } 895 896 unittest 897 { 898 // One ring to rule them all, one ring to find them, 899 // One ring to bring them all and in the darkness bind them. 900 static immutable istring message = 901 "Ash nazg durbatulûk, ash nazg gimbatul, " 902 ~ "ash nazg thrakatulûk, agh burzum-ishi krimpatul."; 903 904 static assert( message.length == 90 ); 905 906 // This compressed data was created using Python 2.5's built in zlib 907 // module, with the default compression level. 908 { 909 static immutable ubyte[] message_z = [ 910 0x78,0x9c,0x73,0x2c,0xce,0x50,0xc8,0x4b, 911 0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a, 912 0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47, 913 0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16, 914 0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46, 915 0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa, 916 0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54, 917 0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea, 918 0x01,0x00,0x1f,0xe3,0x22,0x99]; 919 920 scope cond_z = new Array(2048); 921 scope comp = new ZlibOutput(cond_z); 922 comp.write (message); 923 comp.close; 924 925 test( comp.written == message_z.length ); 926 927 /+ 928 Stdout("message_z:").newline; 929 foreach( b ; cast(ubyte[]) cond_z.slice ) 930 Stdout.format("0x{0:x2},", b); 931 Stdout.newline.newline; 932 +/ 933 934 //assert( message_z == cast(ubyte[])(cond_z.slice) ); 935 check_array!(__FILE__,__LINE__) 936 ( message_z, cast(ubyte[]) cond_z.slice, "message_z " ); 937 938 scope decomp = new ZlibInput(cond_z); 939 auto buffer = new ubyte[256]; 940 buffer = buffer[0 .. decomp.read(buffer)]; 941 942 //assert( cast(ubyte[])message == buffer ); 943 check_array!(__FILE__,__LINE__) 944 ( cast(ubyte[]) message, buffer, "message (zlib) " ); 945 } 946 947 // This compressed data was created using the Cygwin gzip program 948 // with default options. The original file was called "testdata.txt". 949 { 950 static immutable ubyte[] message_gz = [ 951 0x1f,0x8b,0x08,0x00,0x80,0x70,0x6f,0x45, 952 0x00,0x03,0x73,0x2c,0xce,0x50,0xc8,0x4b, 953 0xac,0x4a,0x57,0x48,0x29,0x2d,0x4a,0x4a, 954 0x2c,0x29,0xcd,0x39,0xbc,0x3b,0x5b,0x47, 955 0x21,0x11,0x26,0x9a,0x9e,0x99,0x0b,0x16, 956 0x45,0x12,0x2a,0xc9,0x28,0x4a,0xcc,0x46, 957 0xa8,0x4c,0xcf,0x50,0x48,0x2a,0x2d,0xaa, 958 0x2a,0xcd,0xd5,0xcd,0x2c,0xce,0xc8,0x54, 959 0xc8,0x2e,0xca,0xcc,0x2d,0x00,0xc9,0xea, 960 0x01,0x00,0x45,0x38,0xbc,0x58,0x5a,0x00, 961 0x00,0x00]; 962 963 // Compresses the original message, and outputs the bytes. You can use 964 // this to test the output of ZlibOutput with gzip. If you use this, 965 // don't forget to import Stdout somewhere. 966 /+ 967 scope comp_gz = new Array(2048); 968 scope comp = new ZlibOutput(comp_gz, ZlibOutput.Level.Normal, ZlibOutput.Encoding.Gzip, WINDOWBITS_DEFAULT); 969 comp.write(message); 970 comp.close; 971 972 Stdout.format("message_gz ({0} bytes):", comp_gz.slice.length).newline; 973 foreach( b ; cast(ubyte[]) comp_gz.slice ) 974 Stdout.format("0x{0:x2},", b); 975 Stdout.newline; 976 +/ 977 978 // We aren't going to test that we can compress to a gzip stream 979 // since gzip itself always adds stuff like the filename, timestamps, 980 // etc. We'll just make sure we can DECOMPRESS gzip streams. 981 scope decomp_gz = new Array(message_gz.dup); 982 scope decomp = new ZlibInput(decomp_gz); 983 auto buffer = new ubyte[256]; 984 buffer = buffer[0 .. decomp.read(buffer)]; 985 986 //assert( cast(ubyte[]) message == buffer ); 987 check_array!(__FILE__,__LINE__) 988 ( cast(ubyte[]) message, buffer, "message (gzip) "); 989 } 990 }