1 /** 2 * Compress/decompress data using the zlib library. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.utils.zlib; 15 16 import etc.c.zlib; 17 import std.conv; 18 import std.exception; 19 20 import ae.sys.data; 21 22 class ZlibException : Exception 23 { 24 this(int err, z_stream* zs) 25 { 26 if (zs.msg) 27 super(to!string(zs.msg)); 28 else 29 { 30 string msg; 31 switch (err) 32 { 33 case Z_STREAM_END: msg = "stream end"; break; 34 case Z_NEED_DICT: msg = "need dict"; break; 35 case Z_ERRNO: msg = "errno"; break; 36 case Z_STREAM_ERROR: msg = "stream error"; break; 37 case Z_DATA_ERROR: msg = "data error"; break; 38 case Z_MEM_ERROR: msg = "mem error"; break; 39 case Z_BUF_ERROR: msg = "buf error"; break; 40 case Z_VERSION_ERROR: msg = "version error"; break; 41 default: msg = "unknown error"; break; 42 } 43 super(msg); 44 } 45 } 46 47 this(string msg) { super(msg); } 48 } 49 50 enum ZlibMode { normal, raw, gzipOnly, gzipAuto } 51 52 struct ZlibOptions 53 { 54 int deflateLevel = Z_DEFAULT_COMPRESSION; 55 int windowBits = 15; /// 8..15 - actual windowBits, without additional meaning 56 ZlibMode mode; 57 58 invariant() 59 { 60 assert(deflateLevel == Z_DEFAULT_COMPRESSION || (deflateLevel >= 0 && deflateLevel <= 9)); 61 assert(windowBits >= 8 && windowBits <= 15); 62 } 63 64 private: 65 @property 66 int zwindowBits() 67 { 68 final switch (mode) 69 { 70 case ZlibMode.normal: 71 return windowBits; 72 case ZlibMode.raw: 73 return -windowBits; 74 case ZlibMode.gzipOnly: 75 return 16+windowBits; 76 case ZlibMode.gzipAuto: 77 return 32+windowBits; 78 } 79 } 80 } 81 82 struct ZlibProcess(bool COMPRESSING) 83 { 84 void init(ZlibOptions options = ZlibOptions.init) 85 { 86 static if (COMPRESSING) 87 //zenforce(deflateInit(&zs, options.deflateLevel)); 88 zenforce(deflateInit2(&zs, options.deflateLevel, Z_DEFLATED, options.zwindowBits, 8, Z_DEFAULT_STRATEGY)); 89 else 90 //zenforce(inflateInit(&zs)); 91 zenforce(inflateInit2(&zs, options.zwindowBits)); 92 } 93 94 void processChunk(Data chunk) 95 { 96 if (!chunk.length) 97 return; 98 99 assert(zs.avail_in == 0); 100 zs.next_in = cast(ubyte*) chunk.ptr; 101 zs.avail_in = to!uint(chunk.length); 102 103 do 104 { 105 if (zs.avail_out == 0) 106 allocChunk(adjustSize(zs.avail_in)); 107 108 assert(zs.avail_in && zs.next_in ); 109 assert(zs.avail_out && zs.next_out); 110 if (zend(processFunc(&zs, Z_NO_FLUSH))) 111 enforce(zs.avail_in==0, new ZlibException("Trailing data")); 112 } while (zs.avail_in); 113 } 114 115 Data[] flush() 116 { 117 if (zs.avail_out == 0) 118 allocChunk(adjustSize(zs.avail_in)); 119 120 while (!zend(processFunc(&zs, Z_FINISH))) 121 allocChunk(zs.avail_out*2+1); 122 123 saveChunk(); 124 return outputChunks; 125 } 126 127 static Data[] process(Data[] input, ZlibOptions options = ZlibOptions.init) 128 { 129 typeof(this) zp; 130 zp.init(options); 131 foreach (ref chunk; input) 132 zp.processChunk(chunk); 133 return zp.flush(); 134 } 135 136 static Data process(Data input, ZlibOptions options = ZlibOptions.init) 137 { 138 return process([input], options).joinData(); 139 } 140 141 ~this() 142 { 143 zenforce(endFunc(&zs)); 144 } 145 146 private: 147 z_stream zs; 148 Data currentChunk; 149 Data[] outputChunks; 150 151 static if (COMPRESSING) 152 { 153 alias deflate processFunc; 154 alias deflateEnd endFunc; 155 156 size_t adjustSize(size_t sz) { return sz / 4 + 1; } 157 } 158 else 159 { 160 alias inflate processFunc; 161 alias inflateEnd endFunc; 162 163 size_t adjustSize(size_t sz) { return sz * 4 + 1; } 164 } 165 166 void zenforce(int ret) 167 { 168 if (ret != Z_OK) 169 throw new ZlibException(ret, &zs); 170 } 171 172 bool zend(int ret) 173 { 174 if (ret == Z_STREAM_END) 175 return true; 176 zenforce(ret); 177 return false; 178 } 179 180 void saveChunk() 181 { 182 if (zs.next_out && zs.next_out != currentChunk.ptr) 183 { 184 outputChunks ~= currentChunk[0..zs.next_out-currentChunk.ptr]; 185 currentChunk = Data(); 186 } 187 zs.next_out = null; 188 } 189 190 void allocChunk(size_t sz) 191 { 192 saveChunk(); 193 currentChunk = Data(sz); 194 currentChunk.length = currentChunk.capacity; 195 zs.next_out = cast(ubyte*)currentChunk.mptr; 196 zs.avail_out = to!uint(currentChunk.length); 197 } 198 } 199 200 alias ZlibProcess!true ZlibDeflater; 201 alias ZlibProcess!false ZlibInflater; 202 203 alias ZlibDeflater.process compress; 204 alias ZlibInflater.process uncompress; 205 206 Data compress(Data input, int level) 207 { 208 return compress(input, ZlibOptions(level)); 209 } 210 211 unittest 212 { 213 void testRoundtrip(ubyte[] src) 214 { 215 ubyte[] def = cast(ubyte[]) compress(Data(src)).toHeap; 216 ubyte[] res = cast(ubyte[])uncompress(Data(def)).toHeap; 217 assert(res == src); 218 } 219 220 testRoundtrip(cast(ubyte[]) 221 "the quick brown fox jumps over the lazy dog\r 222 the quick brown fox jumps over the lazy dog\r 223 "); 224 testRoundtrip([0]); 225 testRoundtrip(null); 226 }