1/*
2 * Copyright 2009-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <package/hpkg/v1/PackageDataReader.h>
8
9#include <string.h>
10
11#include <algorithm>
12#include <new>
13
14#include <DataIO.h>
15#include <package/hpkg/BufferPool.h>
16#include <package/hpkg/PoolBuffer.h>
17#include <package/hpkg/v1/HPKGDefsPrivate.h>
18#include <package/hpkg/v1/PackageData.h>
19#include <ZlibCompressionAlgorithm.h>
20
21
22namespace BPackageKit {
23
24namespace BHPKG {
25
26namespace V1 {
27
28
29using BHPKG::BPrivate::PoolBufferPutter;
30
31
32// minimum/maximum zlib chunk size we consider sane
33static const size_t kMinSaneZlibChunkSize = 1024;
34static const size_t kMaxSaneZlibChunkSize = 10 * 1024 * 1024;
35
36// maximum number of entries in the zlib offset table buffer
37static const uint32 kMaxZlibOffsetTableBufferSize = 512;
38
39static const size_t kUncompressedReaderBufferSize
40	= B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
41
42
43// #pragma mark - PackageDataReader
44
45
46class PackageDataReader : public BAbstractBufferedDataReader {
47public:
48	PackageDataReader(BDataReader* dataReader)
49		:
50		fDataReader(dataReader)
51	{
52	}
53
54	virtual ~PackageDataReader()
55	{
56	}
57
58	virtual status_t Init(const BPackageData& data) = 0;
59
60protected:
61			BDataReader*			fDataReader;
62};
63
64
65// #pragma mark - UncompressedPackageDataReader
66
67
68class UncompressedPackageDataReader : public PackageDataReader {
69public:
70	UncompressedPackageDataReader(BDataReader* dataReader,
71		BBufferPool* bufferPool)
72		:
73		PackageDataReader(dataReader),
74		fBufferPool(bufferPool)
75	{
76	}
77
78	status_t Init(const BPackageData& data)
79	{
80		fOffset = data.Offset();
81		fSize = data.UncompressedSize();
82		return B_OK;
83	}
84
85	virtual status_t ReadData(off_t offset, void* buffer, size_t size)
86	{
87		if (size == 0)
88			return B_OK;
89
90		if (offset < 0)
91			return B_BAD_VALUE;
92
93		if ((uint64)offset > fSize || size > fSize - offset)
94			return B_BAD_VALUE;
95
96		return fDataReader->ReadData(fOffset + offset, buffer, size);
97	}
98
99	virtual status_t ReadDataToOutput(off_t offset, size_t size,
100		BDataIO* output)
101	{
102		if (size == 0)
103			return B_OK;
104
105		if (offset < 0)
106			return B_BAD_VALUE;
107
108		if ((uint64)offset > fSize || size > fSize - offset)
109			return B_BAD_VALUE;
110
111		// get a temporary buffer
112		PoolBuffer* buffer = fBufferPool->GetBuffer(
113			kUncompressedReaderBufferSize);
114		if (buffer == NULL)
115			return B_NO_MEMORY;
116		PoolBufferPutter bufferPutter(fBufferPool, &buffer);
117
118		while (size > 0) {
119			// read into the buffer
120			size_t toRead = std::min(size, buffer->Size());
121			status_t error = fDataReader->ReadData(fOffset + offset,
122				buffer->Buffer(), toRead);
123			if (error != B_OK)
124				return error;
125
126			// write to the output
127			error = output->WriteExactly(buffer->Buffer(), toRead);
128			if (error != B_OK)
129				return error;
130
131			offset += toRead;
132			size -= toRead;
133		}
134
135		return B_OK;
136	}
137
138private:
139	BBufferPool*	fBufferPool;
140	uint64			fOffset;
141	uint64			fSize;
142};
143
144
145// #pragma mark - ZlibPackageDataReader
146
147
148class ZlibPackageDataReader : public PackageDataReader {
149public:
150	ZlibPackageDataReader(BDataReader* dataReader, BBufferPool* bufferPool)
151		:
152		PackageDataReader(dataReader),
153		fBufferPool(bufferPool),
154		fUncompressBuffer(NULL),
155		fOffsetTable(NULL)
156	{
157	}
158
159	~ZlibPackageDataReader()
160	{
161		delete[] fOffsetTable;
162
163		fBufferPool->PutBuffer(&fUncompressBuffer);
164	}
165
166	status_t Init(const BPackageData& data)
167	{
168		fOffset = data.Offset();
169		fCompressedSize = data.CompressedSize();
170		fUncompressedSize = data.UncompressedSize();
171		fChunkSize = data.ChunkSize();
172
173		// validate chunk size
174		if (fChunkSize == 0)
175			fChunkSize = B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
176		if (fChunkSize < kMinSaneZlibChunkSize
177			|| fChunkSize > kMaxSaneZlibChunkSize) {
178			return B_BAD_DATA;
179		}
180
181		fChunkCount = (fUncompressedSize + (fChunkSize - 1)) / fChunkSize;
182		fOffsetTableSize = (fChunkCount - 1) * sizeof(uint64);
183		if (fOffsetTableSize >= fCompressedSize)
184			return B_BAD_DATA;
185
186		// allocate a buffer for the offset table
187		if (fChunkCount > 1) {
188			fOffsetTableBufferEntryCount = std::min(fChunkCount - 1,
189				(uint64)kMaxZlibOffsetTableBufferSize);
190			fOffsetTable = new(std::nothrow) uint64[
191				fOffsetTableBufferEntryCount];
192			if (fOffsetTable == NULL)
193				return B_NO_MEMORY;
194
195			fOffsetTableIndex = -1;
196				// mark the table content invalid
197		} else
198			fChunkSize = fUncompressedSize;
199
200		// mark uncompressed content invalid
201		fUncompressedChunk = -1;
202
203		return B_OK;
204	}
205
206	virtual status_t ReadDataToOutput(off_t offset, size_t size,
207		BDataIO* output)
208	{
209		// check offset and size
210		if (size == 0)
211			return B_OK;
212
213		if (offset < 0)
214			return B_BAD_VALUE;
215
216		if ((uint64)offset > fUncompressedSize
217			|| size > fUncompressedSize - offset) {
218			return B_BAD_VALUE;
219		}
220
221		// get our uncompressed chunk buffer back, if possible
222		bool newBuffer;
223		if (fBufferPool->GetBuffer(fChunkSize, &fUncompressBuffer, &newBuffer)
224				== NULL) {
225			return B_NO_MEMORY;
226		}
227		PoolBufferPutter uncompressBufferPutter(fBufferPool,
228			&fUncompressBuffer);
229
230		if (newBuffer)
231			fUncompressedChunk = -1;
232
233		// uncompress
234		int64 chunkIndex = offset / fChunkSize;
235		off_t chunkOffset = chunkIndex * fChunkSize;
236		size_t inChunkOffset = offset - chunkOffset;
237
238		while (size > 0) {
239			// read and uncompress the chunk
240			status_t error = _ReadChunk(chunkIndex);
241			if (error != B_OK)
242				return error;
243
244			// write data to output
245			size_t toCopy = std::min(size, (size_t)fChunkSize - inChunkOffset);
246			error = output->WriteExactly(
247				(uint8*)fUncompressBuffer->Buffer() + inChunkOffset, toCopy);
248			if (error != B_OK)
249				return error;
250
251			size -= toCopy;
252
253			chunkIndex++;
254			chunkOffset += fChunkSize;
255			inChunkOffset = 0;
256		}
257
258		return B_OK;
259	}
260
261private:
262	status_t _ReadChunk(int64 chunkIndex)
263	{
264		if (chunkIndex == fUncompressedChunk)
265			return B_OK;
266
267		// get the chunk offset and size
268		uint64 offset = 0;
269		uint32 compressedSize = 0;
270		status_t error = _GetCompressedChunkOffsetAndSize(chunkIndex, offset,
271			compressedSize);
272		if (error != B_OK)
273			return error;
274
275		uint32 uncompressedSize = (uint64)chunkIndex + 1 < fChunkCount
276			? fChunkSize : fUncompressedSize - chunkIndex * fChunkSize;
277
278		// read the chunk
279		if (compressedSize == uncompressedSize) {
280			// the chunk is not compressed -- read it directly into the
281			// uncompressed buffer
282			error = fDataReader->ReadData(offset, fUncompressBuffer->Buffer(),
283				compressedSize);
284		} else {
285			// read to a read buffer and uncompress
286			PoolBuffer* readBuffer = fBufferPool->GetBuffer(fChunkSize);
287			if (readBuffer == NULL)
288				return B_NO_MEMORY;
289			PoolBufferPutter readBufferPutter(fBufferPool, readBuffer);
290
291			error = fDataReader->ReadData(offset, readBuffer->Buffer(),
292				compressedSize);
293			if (error != B_OK)
294				return error;
295
296			size_t actuallyUncompressedSize;
297			BZlibCompressionAlgorithm().DecompressBuffer(
298				readBuffer->Buffer(), compressedSize,
299				fUncompressBuffer->Buffer(), uncompressedSize,
300				actuallyUncompressedSize);
301			if (error == B_OK && actuallyUncompressedSize != uncompressedSize)
302				error = B_BAD_DATA;
303		}
304
305		if (error != B_OK) {
306			// error reading/decompressing data -- mark the cached data invalid
307			fUncompressedChunk = -1;
308			return error;
309		}
310
311		fUncompressedChunk = chunkIndex;
312		return B_OK;
313	}
314
315	status_t _GetCompressedChunkOffsetAndSize(int64 chunkIndex, uint64& _offset,
316		uint32& _size)
317	{
318		// get the offset
319		uint64 offset;
320		if (chunkIndex == 0) {
321			// first chunk is at 0
322			offset = 0;
323		} else {
324			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex,
325				offset);
326			if (error != B_OK)
327				return error;
328		}
329
330		// get the end offset
331		uint64 endOffset;
332		if ((uint64)chunkIndex + 1 == fChunkCount) {
333			// last chunk end with the end of the data
334			endOffset = fCompressedSize - fOffsetTableSize;
335		} else {
336			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex + 1,
337				endOffset);
338			if (error != B_OK)
339				return error;
340		}
341
342		// sanity check
343		if (endOffset < offset)
344			return B_BAD_DATA;
345
346		_offset = fOffset + fOffsetTableSize + offset;
347		_size = endOffset - offset;
348		return B_OK;
349	}
350
351	status_t _GetCompressedChunkRelativeOffset(int64 chunkIndex,
352		uint64& _offset)
353	{
354		if (fOffsetTableIndex < 0 || fOffsetTableIndex > chunkIndex
355			|| fOffsetTableIndex + fOffsetTableBufferEntryCount <= chunkIndex) {
356			// read the table at the given index, or, if we can, the whole table
357			int64 readAtIndex = fChunkCount - 1 > fOffsetTableBufferEntryCount
358				? chunkIndex : 1;
359			uint32 entriesToRead = std::min(
360				(uint64)fOffsetTableBufferEntryCount,
361				fChunkCount - readAtIndex);
362
363			status_t error = fDataReader->ReadData(
364				fOffset + (readAtIndex - 1) * sizeof(uint64),
365				fOffsetTable, entriesToRead * sizeof(uint64));
366			if (error != B_OK) {
367				fOffsetTableIndex = -1;
368				return error;
369			}
370
371			fOffsetTableIndex = readAtIndex;
372		}
373
374		// get and check the offset
375		_offset = fOffsetTable[chunkIndex - fOffsetTableIndex];
376		if (_offset > fCompressedSize - fOffsetTableSize)
377			return B_BAD_DATA;
378
379		return B_OK;
380	}
381
382private:
383	BBufferPool*	fBufferPool;
384	PoolBuffer*		fUncompressBuffer;
385	int64			fUncompressedChunk;
386
387	uint64			fOffset;
388	uint64			fUncompressedSize;
389	uint64			fCompressedSize;
390	uint64			fOffsetTableSize;
391	uint64			fChunkCount;
392	uint32			fChunkSize;
393	uint32			fOffsetTableBufferEntryCount;
394	uint64*			fOffsetTable;
395	int32			fOffsetTableIndex;
396};
397
398
399// #pragma mark - PackageDataHeapReader
400
401
402class PackageDataInlineReader : public BBufferDataReader {
403public:
404	PackageDataInlineReader(const BPackageData& data)
405		:
406		BBufferDataReader(data.InlineData(), data.UncompressedSize()),
407		fData(data)
408	{
409	}
410
411private:
412	BPackageData	fData;
413};
414
415
416// #pragma mark - BPackageDataReaderFactory
417
418
419BPackageDataReaderFactory::BPackageDataReaderFactory(BBufferPool* bufferPool)
420	:
421	fBufferPool(bufferPool)
422{
423}
424
425
426status_t
427BPackageDataReaderFactory::CreatePackageDataReader(BDataReader* dataReader,
428	const BPackageData& data, BAbstractBufferedDataReader*& _reader)
429{
430	if (data.IsEncodedInline()) {
431		BAbstractBufferedDataReader* reader
432			= new(std::nothrow) PackageDataInlineReader(data);
433		if (reader == NULL)
434			return B_NO_MEMORY;
435
436		_reader = reader;
437		return B_OK;
438	}
439
440	PackageDataReader* reader;
441
442	switch (data.Compression()) {
443		case B_HPKG_COMPRESSION_NONE:
444			reader = new(std::nothrow) UncompressedPackageDataReader(
445				dataReader, fBufferPool);
446			break;
447		case B_HPKG_COMPRESSION_ZLIB:
448			reader = new(std::nothrow) ZlibPackageDataReader(dataReader,
449				fBufferPool);
450			break;
451		default:
452			return B_BAD_VALUE;
453	}
454
455	if (reader == NULL)
456		return B_NO_MEMORY;
457
458	status_t error = reader->Init(data);
459	if (error != B_OK) {
460		delete reader;
461		return error;
462	}
463
464	_reader = reader;
465	return B_OK;
466}
467
468
469}	// namespace V1
470
471}	// namespace BHPKG
472
473}	// namespace BPackageKit
474