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 }