1/*
2 * Copyright 2013-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <package/hpkg/PackageFileHeapAccessorBase.h>
8
9#include <stdlib.h>
10#include <string.h>
11
12#include <algorithm>
13#include <new>
14
15#include <ByteOrder.h>
16#include <DataIO.h>
17#include <package/hpkg/ErrorOutput.h>
18
19#include <AutoDeleter.h>
20#include <CompressionAlgorithm.h>
21
22
23namespace BPackageKit {
24
25namespace BHPKG {
26
27namespace BPrivate {
28
29
30// #pragma mark - OffsetArray
31
32
33PackageFileHeapAccessorBase::OffsetArray::OffsetArray()
34	:
35	fOffsets(NULL)
36{
37}
38
39
40PackageFileHeapAccessorBase::OffsetArray::~OffsetArray()
41{
42	delete[] fOffsets;
43}
44
45
46bool
47PackageFileHeapAccessorBase::OffsetArray::InitUncompressedChunksOffsets(
48	size_t totalChunkCount)
49{
50	if (totalChunkCount <= 1)
51		return true;
52
53	const size_t max32BitChunks = (uint64(1) << 32) / kChunkSize;
54	size_t actual32BitChunks = totalChunkCount;
55	if (totalChunkCount - 1 > max32BitChunks) {
56		actual32BitChunks = max32BitChunks;
57		fOffsets = _AllocateOffsetArray(totalChunkCount, max32BitChunks);
58	} else
59		fOffsets = _AllocateOffsetArray(totalChunkCount, 0);
60
61	if (fOffsets == NULL)
62		return false;
63
64	{
65		uint32 offset = kChunkSize;
66		for (size_t i = 1; i < actual32BitChunks; i++, offset += kChunkSize)
67			fOffsets[i] = offset;
68
69	}
70
71	if (actual32BitChunks < totalChunkCount) {
72		uint64 offset = actual32BitChunks * kChunkSize;
73		uint32* offsets = fOffsets + actual32BitChunks;
74		for (size_t i = actual32BitChunks; i < totalChunkCount;
75				i++, offset += kChunkSize) {
76			*offsets++ = (uint32)offset;
77			*offsets++ = uint32(offset >> 32);
78		}
79	}
80
81	return true;
82}
83
84
85bool
86PackageFileHeapAccessorBase::OffsetArray::InitChunksOffsets(
87	size_t totalChunkCount, size_t baseIndex, const uint16* chunkSizes,
88	size_t chunkCount)
89{
90	if (totalChunkCount <= 1)
91		return true;
92
93	if (fOffsets == NULL) {
94		fOffsets = _AllocateOffsetArray(totalChunkCount, totalChunkCount);
95		if (fOffsets == NULL)
96			return false;
97	}
98
99	uint64 offset = (*this)[baseIndex];
100	for (size_t i = 0; i < chunkCount; i++) {
101		offset += (uint64)B_BENDIAN_TO_HOST_INT16(chunkSizes[i]) + 1;
102			// the stored value is chunkSize - 1
103		size_t index = baseIndex + i + 1;
104			// (baseIndex + i) is the index of the chunk whose size is stored in
105			// chunkSizes[i]. We compute the offset of the following element
106			// which we store at index (baseIndex + i + 1).
107
108		if (offset <= ~(uint32)0) {
109			fOffsets[index] = (uint32)offset;
110		} else {
111			if (fOffsets[0] == 0) {
112				// Not scaled to allow for 64 bit offsets yet. Do that.
113				uint32* newOffsets = _AllocateOffsetArray(totalChunkCount,
114					index);
115				if (newOffsets == NULL)
116					return false;
117
118				fOffsets[0] = index;
119				memcpy(newOffsets, fOffsets, sizeof(newOffsets[0]) * index);
120
121				delete[] fOffsets;
122				fOffsets = newOffsets;
123			}
124
125			index += index - fOffsets[0];
126			fOffsets[index] = (uint32)offset;
127			fOffsets[index + 1] = uint32(offset >> 32);
128		}
129	}
130
131	return true;
132}
133
134
135bool
136PackageFileHeapAccessorBase::OffsetArray::Init(size_t totalChunkCount,
137	const OffsetArray& other)
138{
139	if (other.fOffsets == NULL)
140		return true;
141
142	size_t elementCount = other.fOffsets[0] == 0
143		? totalChunkCount
144		: 2 * totalChunkCount - other.fOffsets[0];
145
146	fOffsets = new(std::nothrow) uint32[elementCount];
147	if (fOffsets == NULL)
148		return false;
149
150	memcpy(fOffsets, other.fOffsets, elementCount * sizeof(fOffsets[0]));
151	return true;
152}
153
154
155/*static*/ uint32*
156PackageFileHeapAccessorBase::OffsetArray::_AllocateOffsetArray(
157	size_t totalChunkCount, size_t offset32BitChunkCount)
158{
159	uint32* offsets = new(std::nothrow) uint32[
160		2 * totalChunkCount - offset32BitChunkCount];
161
162	if (offsets != NULL) {
163		offsets[0] = offset32BitChunkCount == totalChunkCount
164			? 0 : offset32BitChunkCount;
165			// 0 means that all offsets are 32 bit. Otherwise it's the index of
166			// the first 64 bit offset.
167	}
168
169	return offsets;
170}
171
172
173// #pragma mark - PackageFileHeapAccessorBase
174
175
176PackageFileHeapAccessorBase::PackageFileHeapAccessorBase(
177	BErrorOutput* errorOutput, BPositionIO* file, off_t heapOffset,
178	DecompressionAlgorithmOwner* decompressionAlgorithm)
179	:
180	fErrorOutput(errorOutput),
181	fFile(file),
182	fHeapOffset(heapOffset),
183	fCompressedHeapSize(0),
184	fUncompressedHeapSize(0),
185	fDecompressionAlgorithm(decompressionAlgorithm)
186{
187	if (fDecompressionAlgorithm != NULL)
188		fDecompressionAlgorithm->AcquireReference();
189}
190
191
192PackageFileHeapAccessorBase::~PackageFileHeapAccessorBase()
193{
194	if (fDecompressionAlgorithm != NULL)
195		fDecompressionAlgorithm->ReleaseReference();
196}
197
198
199status_t
200PackageFileHeapAccessorBase::ReadDataToOutput(off_t offset, size_t size,
201	BDataIO* output)
202{
203	if (size == 0)
204		return B_OK;
205
206	if (offset < 0 || (uint64)offset > fUncompressedHeapSize
207		|| size > fUncompressedHeapSize - offset) {
208		return B_BAD_VALUE;
209	}
210
211	// allocate buffers for compressed and uncompressed data
212	uint16* compressedDataBuffer = (uint16*)malloc(kChunkSize);
213	uint16* uncompressedDataBuffer = (uint16*)malloc(kChunkSize);
214	MemoryDeleter compressedDataBufferDeleter(compressedDataBuffer);
215	MemoryDeleter uncompressedDataBufferDeleter(uncompressedDataBuffer);
216	if (compressedDataBuffer == NULL || uncompressedDataBuffer == NULL)
217		return B_NO_MEMORY;
218
219	// read the data
220	size_t chunkIndex = size_t(offset / kChunkSize);
221	size_t inChunkOffset = (uint64)offset - (uint64)chunkIndex * kChunkSize;
222	size_t remainingBytes = size;
223
224	while (remainingBytes > 0) {
225		status_t error = ReadAndDecompressChunk(chunkIndex,
226			compressedDataBuffer, uncompressedDataBuffer);
227		if (error != B_OK)
228			return error;
229
230		size_t toWrite = std::min((size_t)kChunkSize - inChunkOffset,
231			remainingBytes);
232			// The last chunk may be shorter than kChunkSize, but since
233			// size (and thus remainingSize) had been clamped, that doesn't
234			// harm.
235		error = output->WriteExactly(
236			(char*)uncompressedDataBuffer + inChunkOffset, toWrite);
237		if (error != B_OK)
238			return error;
239
240		remainingBytes -= toWrite;
241		chunkIndex++;
242		inChunkOffset = 0;
243	}
244
245	return B_OK;
246}
247
248
249status_t
250PackageFileHeapAccessorBase::ReadAndDecompressChunkData(uint64 offset,
251	size_t compressedSize, size_t uncompressedSize, void* compressedDataBuffer,
252	void* uncompressedDataBuffer)
253{
254	// if uncompressed, read directly into the uncompressed data buffer
255	if (compressedSize == uncompressedSize)
256		return ReadFileData(offset, uncompressedDataBuffer, compressedSize);
257
258	// otherwise read into the other buffer and decompress
259	status_t error = ReadFileData(offset, compressedDataBuffer, compressedSize);
260	if (error != B_OK)
261		return error;
262
263	return DecompressChunkData(compressedDataBuffer, compressedSize,
264		uncompressedDataBuffer, uncompressedSize);
265}
266
267
268status_t
269PackageFileHeapAccessorBase::DecompressChunkData(void* compressedDataBuffer,
270	size_t compressedSize, void* uncompressedDataBuffer,
271	size_t uncompressedSize)
272{
273	size_t actualSize;
274	status_t error = fDecompressionAlgorithm->algorithm->DecompressBuffer(
275		compressedDataBuffer, compressedSize, uncompressedDataBuffer,
276		uncompressedSize, actualSize, fDecompressionAlgorithm->parameters);
277	if (error != B_OK) {
278		fErrorOutput->PrintError("Failed to decompress chunk data: %s\n",
279			strerror(error));
280		return error;
281	}
282
283	if (actualSize != uncompressedSize) {
284		fErrorOutput->PrintError("Failed to decompress chunk data: size "
285			"mismatch\n");
286		return B_ERROR;
287	}
288
289	return B_OK;
290}
291
292
293status_t
294PackageFileHeapAccessorBase::ReadFileData(uint64 offset, void* buffer,
295	size_t size)
296{
297	status_t error = fFile->ReadAtExactly(fHeapOffset + (off_t)offset, buffer,
298		size);
299	if (error != B_OK) {
300		fErrorOutput->PrintError("ReadFileData(%" B_PRIu64 ", %p, %zu) failed "
301			"to read data: %s\n", offset, buffer, size, strerror(error));
302		return error;
303	}
304
305	return B_OK;
306}
307
308
309}	// namespace BPrivate
310
311}	// namespace BHPKG
312
313}	// namespace BPackageKit
314