/*
 * Copyright 2009-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include <package/hpkg/v1/PackageDataReader.h>
 
#include <string.h>
 
#include <algorithm>
#include <new>
 
#include <DataIO.h>
#include <package/hpkg/BufferPool.h>
#include <package/hpkg/PoolBuffer.h>
#include <package/hpkg/v1/HPKGDefsPrivate.h>
#include <package/hpkg/v1/PackageData.h>
#include <ZlibCompressionAlgorithm.h>
 
 
namespace BPackageKit {
 
namespace BHPKG {
 
namespace V1 {
 
 
using BHPKG::BPrivate::PoolBufferPutter;
 
 
// minimum/maximum zlib chunk size we consider sane
static const size_t kMinSaneZlibChunkSize = 1024;
static const size_t kMaxSaneZlibChunkSize = 10 * 1024 * 1024;
 
// maximum number of entries in the zlib offset table buffer
static const uint32 kMaxZlibOffsetTableBufferSize = 512;
 
static const size_t kUncompressedReaderBufferSize
	= B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
 
 
// #pragma mark - PackageDataReader
 
 
class PackageDataReader : public BAbstractBufferedDataReader {
public:
	PackageDataReader(BDataReader* dataReader)
		:
		fDataReader(dataReader)
	{
	}
 
	virtual ~PackageDataReader()
	{
	}
 
	virtual status_t Init(const BPackageData& data) = 0;
 
protected:
			BDataReader*			fDataReader;
};
 
 
// #pragma mark - UncompressedPackageDataReader
 
 
class UncompressedPackageDataReader : public PackageDataReader {
public:
	UncompressedPackageDataReader(BDataReader* dataReader,
		BBufferPool* bufferPool)
		:
		PackageDataReader(dataReader),
		fBufferPool(bufferPool)
	{
	}
 
	status_t Init(const BPackageData& data)
	{
		fOffset = data.Offset();
		fSize = data.UncompressedSize();
		return B_OK;
	}
 
	virtual status_t ReadData(off_t offset, void* buffer, size_t size)
	{
		if (size == 0)
			return B_OK;
 
		if (offset < 0)
			return B_BAD_VALUE;
 
		if ((uint64)offset > fSize || size > fSize - offset)
			return B_BAD_VALUE;
 
		return fDataReader->ReadData(fOffset + offset, buffer, size);
	}
 
	virtual status_t ReadDataToOutput(off_t offset, size_t size,
		BDataIO* output)
	{
		if (size == 0)
			return B_OK;
 
		if (offset < 0)
			return B_BAD_VALUE;
 
		if ((uint64)offset > fSize || size > fSize - offset)
			return B_BAD_VALUE;
 
		// get a temporary buffer
		PoolBuffer* buffer = fBufferPool->GetBuffer(
			kUncompressedReaderBufferSize);
		if (buffer == NULL)
			return B_NO_MEMORY;
		PoolBufferPutter bufferPutter(fBufferPool, &buffer);
 
		while (size > 0) {
			// read into the buffer
			size_t toRead = std::min(size, buffer->Size());
			status_t error = fDataReader->ReadData(fOffset + offset,
				buffer->Buffer(), toRead);
			if (error != B_OK)
				return error;
 
			// write to the output
			error = output->WriteExactly(buffer->Buffer(), toRead);
			if (error != B_OK)
				return error;
 
			offset += toRead;
			size -= toRead;
		}
 
		return B_OK;
	}
 
private:
	BBufferPool*	fBufferPool;
	uint64			fOffset;
	uint64			fSize;
};
 
 
// #pragma mark - ZlibPackageDataReader
 
 
class ZlibPackageDataReader : public PackageDataReader {
public:
	ZlibPackageDataReader(BDataReader* dataReader, BBufferPool* bufferPool)
		:
		PackageDataReader(dataReader),
		fBufferPool(bufferPool),
		fUncompressBuffer(NULL),
		fOffsetTable(NULL)
	{
	}
 
	~ZlibPackageDataReader()
	{
		delete[] fOffsetTable;
 
		fBufferPool->PutBuffer(&fUncompressBuffer);
	}
 
	status_t Init(const BPackageData& data)
	{
		fOffset = data.Offset();
		fCompressedSize = data.CompressedSize();
		fUncompressedSize = data.UncompressedSize();
		fChunkSize = data.ChunkSize();
 
		// validate chunk size
		if (fChunkSize == 0)
			fChunkSize = B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
		if (fChunkSize < kMinSaneZlibChunkSize
			|| fChunkSize > kMaxSaneZlibChunkSize) {
			return B_BAD_DATA;
		}
 
		fChunkCount = (fUncompressedSize + (fChunkSize - 1)) / fChunkSize;
		fOffsetTableSize = (fChunkCount - 1) * sizeof(uint64);
		if (fOffsetTableSize >= fCompressedSize)
			return B_BAD_DATA;
 
		// allocate a buffer for the offset table
		if (fChunkCount > 1) {
			fOffsetTableBufferEntryCount = std::min(fChunkCount - 1,
				(uint64)kMaxZlibOffsetTableBufferSize);
			fOffsetTable = new(std::nothrow) uint64[
				fOffsetTableBufferEntryCount];
			if (fOffsetTable == NULL)
				return B_NO_MEMORY;
 
			fOffsetTableIndex = -1;
				// mark the table content invalid
		} else
			fChunkSize = fUncompressedSize;
 
		// mark uncompressed content invalid
		fUncompressedChunk = -1;
 
		return B_OK;
	}
 
	virtual status_t ReadDataToOutput(off_t offset, size_t size,
		BDataIO* output)
	{
		// check offset and size
		if (size == 0)
			return B_OK;
 
		if (offset < 0)
			return B_BAD_VALUE;
 
		if ((uint64)offset > fUncompressedSize
			|| size > fUncompressedSize - offset) {
			return B_BAD_VALUE;
		}
 
		// get our uncompressed chunk buffer back, if possible
		bool newBuffer;
		if (fBufferPool->GetBuffer(fChunkSize, &fUncompressBuffer, &newBuffer)
				== NULL) {
			return B_NO_MEMORY;
		}
		PoolBufferPutter uncompressBufferPutter(fBufferPool,
			&fUncompressBuffer);
 
		if (newBuffer)
			fUncompressedChunk = -1;
 
		// uncompress
		int64 chunkIndex = offset / fChunkSize;
		off_t chunkOffset = chunkIndex * fChunkSize;
		size_t inChunkOffset = offset - chunkOffset;
 
		while (size > 0) {
			// read and uncompress the chunk
			status_t error = _ReadChunk(chunkIndex);
			if (error != B_OK)
				return error;
 
			// write data to output
			size_t toCopy = std::min(size, (size_t)fChunkSize - inChunkOffset);
			error = output->WriteExactly(
				(uint8*)fUncompressBuffer->Buffer() + inChunkOffset, toCopy);
			if (error != B_OK)
				return error;
 
			size -= toCopy;
 
			chunkIndex++;
			chunkOffset += fChunkSize;
			inChunkOffset = 0;
		}
 
		return B_OK;
	}
 
private:
	status_t _ReadChunk(int64 chunkIndex)
	{
		if (chunkIndex == fUncompressedChunk)
			return B_OK;
 
		// get the chunk offset and size
		uint64 offset = 0;
		uint32 compressedSize = 0;
		status_t error = _GetCompressedChunkOffsetAndSize(chunkIndex, offset,
			compressedSize);
		if (error != B_OK)
			return error;
 
		uint32 uncompressedSize = (uint64)chunkIndex + 1 < fChunkCount
			? fChunkSize : fUncompressedSize - chunkIndex * fChunkSize;
 
		// read the chunk
		if (compressedSize == uncompressedSize) {
			// the chunk is not compressed -- read it directly into the
			// uncompressed buffer
			error = fDataReader->ReadData(offset, fUncompressBuffer->Buffer(),
				compressedSize);
		} else {
			// read to a read buffer and uncompress
			PoolBuffer* readBuffer = fBufferPool->GetBuffer(fChunkSize);
			if (readBuffer == NULL)
				return B_NO_MEMORY;
			PoolBufferPutter readBufferPutter(fBufferPool, readBuffer);
 
			error = fDataReader->ReadData(offset, readBuffer->Buffer(),
				compressedSize);
			if (error != B_OK)
				return error;
 
			size_t actuallyUncompressedSize;
			BZlibCompressionAlgorithm().DecompressBuffer(
				readBuffer->Buffer(), compressedSize,
				fUncompressBuffer->Buffer(), uncompressedSize,
				actuallyUncompressedSize);
			if (error == B_OK && actuallyUncompressedSize != uncompressedSize)
				error = B_BAD_DATA;
		}
 
		if (error != B_OK) {
			// error reading/decompressing data -- mark the cached data invalid
			fUncompressedChunk = -1;
			return error;
		}
 
		fUncompressedChunk = chunkIndex;
		return B_OK;
	}
 
	status_t _GetCompressedChunkOffsetAndSize(int64 chunkIndex, uint64& _offset,
		uint32& _size)
	{
		// get the offset
		uint64 offset;
		if (chunkIndex == 0) {
			// first chunk is at 0
			offset = 0;
		} else {
			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex,
				offset);
			if (error != B_OK)
				return error;
		}
 
		// get the end offset
		uint64 endOffset;
		if ((uint64)chunkIndex + 1 == fChunkCount) {
			// last chunk end with the end of the data
			endOffset = fCompressedSize - fOffsetTableSize;
		} else {
			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex + 1,
				endOffset);
			if (error != B_OK)
				return error;
		}
 
		// sanity check
		if (endOffset < offset)
			return B_BAD_DATA;
 
		_offset = fOffset + fOffsetTableSize + offset;
		_size = endOffset - offset;
		return B_OK;
	}
 
	status_t _GetCompressedChunkRelativeOffset(int64 chunkIndex,
		uint64& _offset)
	{
		if (fOffsetTableIndex < 0 || fOffsetTableIndex > chunkIndex
			|| fOffsetTableIndex + fOffsetTableBufferEntryCount <= chunkIndex) {
			// read the table at the given index, or, if we can, the whole table
			int64 readAtIndex = fChunkCount - 1 > fOffsetTableBufferEntryCount
				? chunkIndex : 1;
			uint32 entriesToRead = std::min(
				(uint64)fOffsetTableBufferEntryCount,
				fChunkCount - readAtIndex);
 
			status_t error = fDataReader->ReadData(
				fOffset + (readAtIndex - 1) * sizeof(uint64),
				fOffsetTable, entriesToRead * sizeof(uint64));
			if (error != B_OK) {
				fOffsetTableIndex = -1;
				return error;
			}
 
			fOffsetTableIndex = readAtIndex;
		}
 
		// get and check the offset
		_offset = fOffsetTable[chunkIndex - fOffsetTableIndex];
		if (_offset > fCompressedSize - fOffsetTableSize)
			return B_BAD_DATA;
 
		return B_OK;
	}
 
private:
	BBufferPool*	fBufferPool;
	PoolBuffer*		fUncompressBuffer;
	int64			fUncompressedChunk;
 
	uint64			fOffset;
	uint64			fUncompressedSize;
	uint64			fCompressedSize;
	uint64			fOffsetTableSize;
	uint64			fChunkCount;
	uint32			fChunkSize;
	uint32			fOffsetTableBufferEntryCount;
	uint64*			fOffsetTable;
	int32			fOffsetTableIndex;
};
 
 
// #pragma mark - PackageDataHeapReader
 
 
class PackageDataInlineReader : public BBufferDataReader {
public:
	PackageDataInlineReader(const BPackageData& data)
		:
		BBufferDataReader(data.InlineData(), data.UncompressedSize()),
		fData(data)
	{
	}
 
private:
	BPackageData	fData;
};
 
 
// #pragma mark - BPackageDataReaderFactory
 
 
BPackageDataReaderFactory::BPackageDataReaderFactory(BBufferPool* bufferPool)
	:
	fBufferPool(bufferPool)
{
}
 
 
status_t
BPackageDataReaderFactory::CreatePackageDataReader(BDataReader* dataReader,
	const BPackageData& data, BAbstractBufferedDataReader*& _reader)
{
	if (data.IsEncodedInline()) {
		BAbstractBufferedDataReader* reader
			= new(std::nothrow) PackageDataInlineReader(data);
		if (reader == NULL)
			return B_NO_MEMORY;
 
		_reader = reader;
		return B_OK;
	}
 
	PackageDataReader* reader;
 
	switch (data.Compression()) {
		case B_HPKG_COMPRESSION_NONE:
			reader = new(std::nothrow) UncompressedPackageDataReader(
				dataReader, fBufferPool);
			break;
		case B_HPKG_COMPRESSION_ZLIB:
			reader = new(std::nothrow) ZlibPackageDataReader(dataReader,
				fBufferPool);
			break;
		default:
			return B_BAD_VALUE;
	}
 
	if (reader == NULL)
		return B_NO_MEMORY;
 
	status_t error = reader->Init(data);
	if (error != B_OK) {
		delete reader;
		return error;
	}
 
	_reader = reader;
	return B_OK;
}
 
 
}	// namespace V1
 
}	// namespace BHPKG
 
}	// namespace BPackageKit

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fOffset, fSize.