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