1 /*
2 * Compression Transform
3 * 
4 * Copyright:
5 * (C) 2014 Jack Lloyd
6 * (C) 2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 
12 module botan.compression.compress;
13 
14 import botan.constants;
15 
16 import botan.algo_base.transform;
17 import botan.utils.types;
18 import botan.utils.mem_ops;
19 public import botan.compression.compress_utils;
20 import botan.compression.zlib;
21 import botan.compression.lzma;
22 import botan.compression.bzip2;
23 
24 abstract class CompressorTransform : Transformation
25 {
26 public:
27     override size_t updateGranularity() const { return 1; }
28     
29     override size_t minimumFinalSize() const { return 0; }
30     
31     override size_t defaultNonceLength() const { return 0; }
32 
33     override bool validNonceLength(size_t nonce_len) const { return nonce_len == 0; }
34     
35     abstract void flush(ref SecureVector!ubyte buf, size_t offset = 0) { update(buf, offset); }
36     
37     override size_t outputLength(size_t) const 
38     {
39         throw new Exception(name() ~ " output length indeterminate");
40     }
41 }
42 
43 interface CompressionStream
44 {
45 public:
46     void nextIn(ubyte* b, size_t len);
47     
48     void nextOut(ubyte* b, size_t len);
49     
50     size_t availIn() const;
51     
52     size_t availOut() const;
53     
54     uint runFlag() const;
55     uint flushFlag() const;
56     uint finishFlag() const;
57     
58     bool run(uint flags);
59 }
60 
61 abstract class StreamCompression : CompressorTransform, Transformation
62 {
63 public:
64     void update(ref SecureVector!ubyte buf, size_t offset = 0) { process(buf, offset, m_stream.runFlag()); }
65     
66     override void flush(ref SecureVector!ubyte buf, size_t offset = 0) { process(buf, offset, m_stream.flushFlag()); }
67     
68     void finish(ref SecureVector!ubyte buf, size_t offset = 0)
69     {
70         process(buf, offset, m_stream.finishFlag());
71         clear();
72     }
73 
74     override size_t updateGranularity() const { return 1; }
75     override size_t minimumFinalSize() const { return 0; }    
76     override size_t defaultNonceLength() const { return 0; }    
77     override bool validNonceLength(size_t nonce_len) const { return nonce_len == 0; }
78     override size_t outputLength(size_t input_length) const { return super.outputLength(input_length); }
79     
80     void clear() { m_stream.free(); }
81 
82 protected:
83     abstract CompressionStream makeStream() const;
84 
85     override SecureVector!ubyte startRaw(const(ubyte)* data, size_t nonce_len)
86     {
87         if (!validNonceLength(nonce_len))
88             throw new InvalidIVLength(name(), nonce_len);
89 
90         m_stream = makeStream();
91         return SecureVector!ubyte();
92     }
93     
94     void process(ref SecureVector!ubyte buf, size_t offset, uint flags)
95     {
96         assert(m_stream, "Initialized");
97         assert(buf.length >= offset, "Offset is sane");
98         
99         if (m_buffer.length < buf.length + offset)
100             m_buffer.resize(buf.length + offset);
101         
102         m_stream.nextIn(buf.ptr + offset, buf.length - offset);
103         m_stream.nextOut(m_buffer.ptr + offset, m_buffer.length - offset);
104         
105         while(true)
106         {
107             m_stream.run(flags);
108             
109             if (m_stream.availOut() == 0)
110             {
111                 const size_t added = 8 + m_buffer.length;
112                 m_buffer.resize(m_buffer.length + added);
113                 m_stream.nextOut(&m_buffer[m_buffer.length - added], added);
114             }
115             else if (m_stream.availIn() == 0)
116             {
117                 m_buffer.resize(m_buffer.length - m_stream.availOut());
118                 break;
119             }
120         }
121         
122         copyMem(m_buffer.ptr + 0, buf.ptr + 0, offset);
123         buf.swap(m_buffer);
124     }
125 
126     
127     SecureVector!ubyte m_buffer;
128     Unique!CompressionStream m_stream;
129 }
130 
131 abstract class StreamDecompression : CompressorTransform, Transformation
132 {
133 public:
134     void update(ref SecureVector!ubyte buf, size_t offset = 0)
135     {
136         process(buf, offset, m_stream.runFlag());
137     }
138     
139     void finish(ref SecureVector!ubyte buf, size_t offset = 0)
140     {
141         if (buf.length != offset || m_stream.get())
142             process(buf, offset, m_stream.finishFlag());
143         
144         if (m_stream.get())
145             throw new Exception(name() ~ " finished but not at stream end");
146     }
147     
148     void clear() { m_stream.free(); }
149     override size_t updateGranularity() const { return 1; }
150     override size_t minimumFinalSize() const { return 0; }    
151     override size_t defaultNonceLength() const { return 0; }    
152     override bool validNonceLength(size_t nonce_len) const { return nonce_len == 0; }
153     override size_t outputLength(size_t input_length) const { return super.outputLength(input_length); }
154     
155 protected:
156     override SecureVector!ubyte startRaw(const(ubyte)* data, size_t nonce_len)
157     {
158         if (!validNonceLength(nonce_len))
159             throw new InvalidIVLength(name(), nonce_len);
160         
161         m_stream = makeStream();
162         
163         return SecureVector!ubyte();
164     }
165     
166     void process(ref SecureVector!ubyte buf, size_t offset, uint flags)
167     {
168         assert(m_stream, "Initialized");
169         assert(buf.length >= offset, "Offset is sane");
170         
171         if (m_buffer.length < buf.length + offset)
172             m_buffer.resize(buf.length + offset);
173         
174         m_stream.nextIn(buf.ptr + offset, buf.length - offset);
175         m_stream.nextOut(m_buffer.ptr + offset, m_buffer.length - offset);
176         
177         while(true)
178         {
179             const bool stream_end = m_stream.run(flags);
180             
181             if (stream_end)
182             {
183                 if (m_stream.availIn() == 0) // all data consumed?
184                 {
185                     m_buffer.resize(m_buffer.length - m_stream.availOut());
186                     clear();
187                     break;
188                 }
189                 
190                 // More data follows: try to process as a following stream
191                 const size_t read = (buf.length - offset) - m_stream.availIn();
192                 start();
193                 m_stream.nextIn(buf.ptr + offset + read, buf.length - offset - read);
194             }
195             
196             if (m_stream.availOut() == 0)
197             {
198                 const size_t added = 8 + m_buffer.length;
199                 m_buffer.resize(m_buffer.length + added);
200                 m_stream.nextOut(&m_buffer[m_buffer.length - added], added);
201             }
202             else if (m_stream.availIn() == 0)
203             {
204                 m_buffer.resize(m_buffer.length - m_stream.availOut());
205                 break;
206             }
207         }
208         
209         copyMem(m_buffer.ptr, buf.ptr, offset);
210         buf.swap(m_buffer);
211     }
212     
213     abstract CompressionStream makeStream() const;
214     
215     SecureVector!ubyte m_buffer;
216     Unique!CompressionStream m_stream;
217 }
218 
219 
220 CompressorTransform makeCompressor(in string type, size_t level)
221 {
222     static if (BOTAN_HAS_ZLIB)
223         
224     {
225         if (type == "zlib")
226             return new ZlibCompression(level);
227         if (type == "deflate")
228             return new DeflateCompression(level);
229     }
230     
231     static if (BOTAN_HAS_BZIP2)
232     {
233         if (type == "bzip2")
234             return new Bzip2Compression(level);
235     }
236     
237     static if (BOTAN_HAS_LZMA)
238     {
239         if (type == "lzma")
240             return new LZMACompression(level);
241     }
242     
243     throw new Exception("Unknown compression type " ~ type);
244 }
245 
246 CompressorTransform makeDecompressor(in string type)
247 {
248     static if (BOTAN_HAS_ZLIB)
249     {
250         if (type == "zlib")
251             return new ZlibDecompression();
252         if (type == "deflate")
253             return new DeflateDecompression();
254     }
255     
256     static if (BOTAN_HAS_BZIP2)
257     {
258         if (type == "bzip2")
259             return new Bzip2Decompression;
260     }
261     
262     static if (BOTAN_HAS_LZMA)
263     {
264         if (type == "lzma")
265             return new LZMADecompression;
266     }
267 
268     throw new Exception("Unknown compression type " ~ type);
269 }
270 
271 static if (BOTAN_HAS_TESTS && !SKIP_COMPRESSION_TEST) unittest {  
272     logDebug("Testing compress.d ...");  
273 	{
274 		static if (BOTAN_HAS_ZLIB) {
275 			Unique!CompressorTransform comp = makeCompressor("zlib", 9); // level 1-9
276 			SecureVector!ubyte buf;
277 			SecureVector!ubyte verif;
278 			buf ~= "Some message";
279 			verif = buf.dup;
280 			comp.start();
281 			comp.finish(buf);
282 			
283 			Unique!CompressorTransform dec = makeDecompressor("zlib");
284 			dec.start();
285 			dec.finish(buf);
286 			assert(buf == verif);
287 			logDebug("Zlib ... PASSED");
288 		}
289 	}
290 	{
291 		static if (BOTAN_HAS_ZLIB) {
292 			Unique!CompressorTransform comp = makeCompressor("deflate", 9); // level 1-9
293 			SecureVector!ubyte buf;
294 			SecureVector!ubyte verif;
295 			buf ~= "Some message";
296 			verif = buf.dup;
297 			comp.start();
298 			comp.finish(buf);
299 			
300 			Unique!CompressorTransform dec = makeDecompressor("deflate");
301 			dec.start();
302 			dec.finish(buf);
303 			assert(buf == verif);
304 			logDebug("Deflate ... PASSED");
305 		}
306 	}
307 	{
308 		static if (BOTAN_HAS_BZIP2) {
309 			Unique!CompressorTransform comp = makeCompressor("bzip2", 9); // level 1-9
310 			SecureVector!ubyte buf;
311 			SecureVector!ubyte verif;
312 			buf ~= "Some message";
313 			verif = buf.dup;
314 			comp.start();
315 			comp.finish(buf);
316 			
317 			Unique!CompressorTransform dec = makeDecompressor("bzip2");
318 			dec.start();
319 			dec.finish(buf);
320 			assert(buf == verif);
321 			logDebug("Bzip2 ... PASSED");
322 		}
323 	}
324 	{
325 		static if (BOTAN_HAS_LZMA) {
326 			Unique!CompressorTransform comp = makeCompressor("lzma", 9); // level 1-9
327 			SecureVector!ubyte buf;
328 			SecureVector!ubyte verif;
329 			buf ~= "Some message ";
330 			verif = buf.dup;
331 			comp.start();
332 			comp.finish(buf);
333 			Unique!CompressorTransform dec = makeDecompressor("lzma");
334 			dec.start();
335 			dec.finish(buf);
336 			assert(buf == verif);
337 			logDebug("LZMA ... PASSED");
338 		}
339 	}
340 }