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