1/*
2 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ZlibCompressionAlgorithm.h>
8
9#include <errno.h>
10#include <string.h>
11
12#include <algorithm>
13#include <new>
14
15#include <zlib.h>
16
17#include <DataIO.h>
18
19
20// build compression support only for userland
21#if !defined(_KERNEL_MODE) && !defined(_BOOT_MODE)
22#	define B_ZLIB_COMPRESSION_SUPPORT 1
23#endif
24
25
26static const size_t kMinBufferSize		= 1024;
27static const size_t kMaxBufferSize		= 1024 * 1024;
28static const size_t kDefaultBufferSize	= 4 * 1024;
29
30
31static size_t
32sanitize_buffer_size(size_t size)
33{
34	if (size < kMinBufferSize)
35		return kMinBufferSize;
36	return std::min(size, kMaxBufferSize);
37}
38
39
40// #pragma mark - BZlibCompressionParameters
41
42
43BZlibCompressionParameters::BZlibCompressionParameters(
44	int compressionLevel)
45	:
46	BCompressionParameters(),
47	fCompressionLevel(compressionLevel),
48	fBufferSize(kDefaultBufferSize),
49	fGzipFormat(false)
50{
51}
52
53
54BZlibCompressionParameters::~BZlibCompressionParameters()
55{
56}
57
58
59int32
60BZlibCompressionParameters::CompressionLevel() const
61{
62	return fCompressionLevel;
63}
64
65
66void
67BZlibCompressionParameters::SetCompressionLevel(int32 level)
68{
69	fCompressionLevel = level;
70}
71
72
73size_t
74BZlibCompressionParameters::BufferSize() const
75{
76	return fBufferSize;
77}
78
79
80void
81BZlibCompressionParameters::SetBufferSize(size_t size)
82{
83	fBufferSize = sanitize_buffer_size(size);
84}
85
86
87bool
88BZlibCompressionParameters::IsGzipFormat() const
89{
90	return fGzipFormat;
91}
92
93
94void
95BZlibCompressionParameters::SetGzipFormat(bool gzipFormat)
96{
97	fGzipFormat = gzipFormat;
98}
99
100
101// #pragma mark - BZlibDecompressionParameters
102
103
104BZlibDecompressionParameters::BZlibDecompressionParameters()
105	:
106	BDecompressionParameters(),
107	fBufferSize(kDefaultBufferSize)
108{
109}
110
111
112BZlibDecompressionParameters::~BZlibDecompressionParameters()
113{
114}
115
116
117size_t
118BZlibDecompressionParameters::BufferSize() const
119{
120	return fBufferSize;
121}
122
123
124void
125BZlibDecompressionParameters::SetBufferSize(size_t size)
126{
127	fBufferSize = sanitize_buffer_size(size);
128}
129
130
131// #pragma mark - CompressionStrategy
132
133
134#ifdef B_ZLIB_COMPRESSION_SUPPORT
135
136
137struct BZlibCompressionAlgorithm::CompressionStrategy {
138	typedef BZlibCompressionParameters Parameters;
139
140	static const bool kNeedsFinalFlush = true;
141
142	static int Init(z_stream& stream,
143		const BZlibCompressionParameters* parameters)
144	{
145		int32 compressionLevel = B_ZLIB_COMPRESSION_DEFAULT;
146		bool gzipFormat = false;
147		if (parameters != NULL) {
148			compressionLevel = parameters->CompressionLevel();
149			gzipFormat = parameters->IsGzipFormat();
150		}
151
152		return deflateInit2(&stream, compressionLevel,
153			Z_DEFLATED,
154			MAX_WBITS + (gzipFormat ? 16 : 0),
155			MAX_MEM_LEVEL,
156			Z_DEFAULT_STRATEGY);
157	}
158
159	static void Uninit(z_stream& stream)
160	{
161		deflateEnd(&stream);
162	}
163
164	static int Process(z_stream& stream, bool flush)
165	{
166		return deflate(&stream, flush ? Z_FINISH : 0);
167	}
168};
169
170
171#endif	// B_ZLIB_COMPRESSION_SUPPORT
172
173
174// #pragma mark - DecompressionStrategy
175
176
177struct BZlibCompressionAlgorithm::DecompressionStrategy {
178	typedef BZlibDecompressionParameters Parameters;
179
180	static const bool kNeedsFinalFlush = false;
181
182	static int Init(z_stream& stream,
183		const BZlibDecompressionParameters* /*parameters*/)
184	{
185		// auto-detect zlib/gzip header
186		return inflateInit2(&stream, 32 + MAX_WBITS);
187	}
188
189	static void Uninit(z_stream& stream)
190	{
191		inflateEnd(&stream);
192	}
193
194	static int Process(z_stream& stream, bool flush)
195	{
196		return inflate(&stream, flush ? Z_FINISH : 0);
197	}
198};
199
200
201// #pragma mark - Stream
202
203
204template<typename BaseClass, typename Strategy>
205struct BZlibCompressionAlgorithm::Stream : BaseClass {
206	Stream(BDataIO* io)
207		:
208		BaseClass(io),
209		fStreamInitialized(false)
210	{
211	}
212
213	~Stream()
214	{
215		if (fStreamInitialized) {
216			if (Strategy::kNeedsFinalFlush)
217				this->Flush();
218			Strategy::Uninit(fStream);
219		}
220	}
221
222	status_t Init(const typename Strategy::Parameters* parameters)
223	{
224		status_t error = this->BaseClass::Init(
225			parameters != NULL ? parameters->BufferSize() : kDefaultBufferSize);
226		if (error != B_OK)
227			return error;
228
229		memset(&fStream, 0, sizeof(fStream));
230
231		int zlibError = Strategy::Init(fStream, parameters);
232		if (zlibError != Z_OK)
233			return _TranslateZlibError(zlibError);
234
235		fStreamInitialized = true;
236		return B_OK;
237	}
238
239	virtual status_t ProcessData(const void* input, size_t inputSize,
240		void* output, size_t outputSize, size_t& bytesConsumed,
241		size_t& bytesProduced)
242	{
243		return _ProcessData(input, inputSize, output, outputSize,
244			bytesConsumed, bytesProduced, false);
245	}
246
247	virtual status_t FlushPendingData(void* output, size_t outputSize,
248		size_t& bytesProduced)
249	{
250		size_t bytesConsumed;
251		return _ProcessData(NULL, 0, output, outputSize,
252			bytesConsumed, bytesProduced, true);
253	}
254
255	template<typename BaseParameters>
256	static status_t Create(BDataIO* io, BaseParameters* _parameters,
257		BDataIO*& _stream)
258	{
259		const typename Strategy::Parameters* parameters
260#ifdef _BOOT_MODE
261			= static_cast<const typename Strategy::Parameters*>(_parameters);
262#else
263			= dynamic_cast<const typename Strategy::Parameters*>(_parameters);
264#endif
265		Stream* stream = new(std::nothrow) Stream(io);
266		if (stream == NULL)
267			return B_NO_MEMORY;
268
269		status_t error = stream->Init(parameters);
270		if (error != B_OK) {
271			delete stream;
272			return error;
273		}
274
275		_stream = stream;
276		return B_OK;
277	}
278
279private:
280	status_t _ProcessData(const void* input, size_t inputSize,
281		void* output, size_t outputSize, size_t& bytesConsumed,
282		size_t& bytesProduced, bool flush)
283	{
284		fStream.next_in = (Bytef*)input;
285		fStream.avail_in = inputSize;
286		fStream.next_out = (Bytef*)output;
287		fStream.avail_out = outputSize;
288
289		int zlibError = Strategy::Process(fStream, flush);
290		if (zlibError != Z_OK) {
291			if (zlibError == Z_STREAM_END) {
292				if (fStream.avail_in != 0)
293					return B_BAD_DATA;
294			} else
295				return _TranslateZlibError(zlibError);
296		}
297
298		bytesConsumed = inputSize - (size_t)fStream.avail_in;
299		bytesProduced = outputSize - (size_t)fStream.avail_out;
300		return B_OK;
301	}
302
303private:
304	z_stream	fStream;
305	bool		fStreamInitialized;
306};
307
308
309// #pragma mark - BZlibCompressionAlgorithm
310
311
312BZlibCompressionAlgorithm::BZlibCompressionAlgorithm()
313	:
314	BCompressionAlgorithm()
315{
316}
317
318
319BZlibCompressionAlgorithm::~BZlibCompressionAlgorithm()
320{
321}
322
323
324status_t
325BZlibCompressionAlgorithm::CreateCompressingInputStream(BDataIO* input,
326	const BCompressionParameters* parameters, BDataIO*& _stream)
327{
328#ifdef B_ZLIB_COMPRESSION_SUPPORT
329	return Stream<BAbstractInputStream, CompressionStrategy>::Create(
330		input, parameters, _stream);
331#else
332	return B_NOT_SUPPORTED;
333#endif
334}
335
336
337status_t
338BZlibCompressionAlgorithm::CreateCompressingOutputStream(BDataIO* output,
339	const BCompressionParameters* parameters, BDataIO*& _stream)
340{
341#ifdef B_ZLIB_COMPRESSION_SUPPORT
342	return Stream<BAbstractOutputStream, CompressionStrategy>::Create(
343		output, parameters, _stream);
344#else
345	return B_NOT_SUPPORTED;
346#endif
347}
348
349
350status_t
351BZlibCompressionAlgorithm::CreateDecompressingInputStream(BDataIO* input,
352	const BDecompressionParameters* parameters, BDataIO*& _stream)
353{
354	return Stream<BAbstractInputStream, DecompressionStrategy>::Create(
355		input, parameters, _stream);
356}
357
358
359status_t
360BZlibCompressionAlgorithm::CreateDecompressingOutputStream(BDataIO* output,
361	const BDecompressionParameters* parameters, BDataIO*& _stream)
362{
363	return Stream<BAbstractOutputStream, DecompressionStrategy>::Create(
364		output, parameters, _stream);
365}
366
367
368status_t
369BZlibCompressionAlgorithm::CompressBuffer(const void* input,
370	size_t inputSize, void* output, size_t outputSize, size_t& _compressedSize,
371	const BCompressionParameters* parameters)
372{
373#ifdef B_ZLIB_COMPRESSION_SUPPORT
374	const BZlibCompressionParameters* zlibParameters
375		= dynamic_cast<const BZlibCompressionParameters*>(parameters);
376	int compressionLevel = zlibParameters != NULL
377		? zlibParameters->CompressionLevel()
378		: B_ZLIB_COMPRESSION_DEFAULT;
379
380	uLongf bytesUsed = outputSize;
381	int zlibError = compress2((Bytef*)output, &bytesUsed, (const Bytef*)input,
382		(uLong)inputSize, compressionLevel);
383	if (zlibError != Z_OK)
384		return _TranslateZlibError(zlibError);
385
386	_compressedSize = (size_t)bytesUsed;
387	return B_OK;
388#else
389	return B_NOT_SUPPORTED;
390#endif
391}
392
393
394status_t
395BZlibCompressionAlgorithm::DecompressBuffer(const void* input,
396	size_t inputSize, void* output, size_t outputSize,
397	size_t& _uncompressedSize, const BDecompressionParameters* parameters)
398{
399	uLongf bytesUsed = outputSize;
400	int zlibError = uncompress((Bytef*)output, &bytesUsed, (const Bytef*)input,
401		(uLong)inputSize);
402	if (zlibError != Z_OK)
403		return _TranslateZlibError(zlibError);
404
405	_uncompressedSize = (size_t)bytesUsed;
406	return B_OK;
407}
408
409
410/*static*/ status_t
411BZlibCompressionAlgorithm::_TranslateZlibError(int error)
412{
413	switch (error) {
414		case Z_OK:
415			return B_OK;
416		case Z_STREAM_END:
417		case Z_NEED_DICT:
418			// a special event (no error), but the caller doesn't seem to handle
419			// it
420			return B_ERROR;
421		case Z_ERRNO:
422			return errno;
423		case Z_STREAM_ERROR:
424			return B_BAD_VALUE;
425		case Z_DATA_ERROR:
426			return B_BAD_DATA;
427		case Z_MEM_ERROR:
428			return B_NO_MEMORY;
429		case Z_BUF_ERROR:
430			return B_BUFFER_OVERFLOW;
431		case Z_VERSION_ERROR:
432			return B_BAD_VALUE;
433		default:
434			return B_ERROR;
435	}
436}
437