michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include "SeekableZStream.h" michael@0: #include "Logging.h" michael@0: michael@0: bool michael@0: SeekableZStream::Init(const void *buf, size_t length) michael@0: { michael@0: const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf); michael@0: if (!header) { michael@0: LOG("Not a seekable zstream"); michael@0: return false; michael@0: } michael@0: michael@0: buffer = reinterpret_cast(buf); michael@0: totalSize = header->totalSize; michael@0: chunkSize = header->chunkSize; michael@0: lastChunkSize = header->lastChunkSize; michael@0: windowBits = header->windowBits; michael@0: dictionary.Init(buffer + sizeof(SeekableZStreamHeader), header->dictSize); michael@0: offsetTable.Init(buffer + sizeof(SeekableZStreamHeader) + header->dictSize, michael@0: header->nChunks); michael@0: filter = GetFilter(header->filter); michael@0: michael@0: /* Sanity check */ michael@0: if ((chunkSize == 0) || michael@0: (!IsPageAlignedSize(chunkSize)) || michael@0: (chunkSize > 8 * PageSize()) || michael@0: (offsetTable.numElements() < 1) || michael@0: (lastChunkSize == 0) || michael@0: (lastChunkSize > chunkSize) || michael@0: (length < totalSize)) { michael@0: LOG("Malformed or broken seekable zstream"); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SeekableZStream::Decompress(void *where, size_t chunk, size_t length) michael@0: { michael@0: while (length) { michael@0: size_t len = std::min(length, static_cast(chunkSize)); michael@0: if (!DecompressChunk(where, chunk, len)) michael@0: return false; michael@0: where = reinterpret_cast(where) + len; michael@0: length -= len; michael@0: chunk++; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length) michael@0: { michael@0: if (chunk >= offsetTable.numElements()) { michael@0: LOG("DecompressChunk: chunk #%" PRIdSize " out of range [0-%" PRIdSize ")", michael@0: chunk, offsetTable.numElements()); michael@0: return false; michael@0: } michael@0: michael@0: bool isLastChunk = (chunk == offsetTable.numElements() - 1); michael@0: michael@0: size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize; michael@0: michael@0: if (length == 0 || length > chunkLen) michael@0: length = chunkLen; michael@0: michael@0: DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")", michael@0: chunk, where, length, chunkLen); michael@0: z_stream zStream; michael@0: memset(&zStream, 0, sizeof(zStream)); michael@0: zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1])) michael@0: - uint32_t(offsetTable[chunk]); michael@0: zStream.next_in = const_cast(buffer + uint32_t(offsetTable[chunk])); michael@0: zStream.avail_out = length; michael@0: zStream.next_out = reinterpret_cast(where); michael@0: michael@0: /* Decompress chunk */ michael@0: if (inflateInit2(&zStream, windowBits) != Z_OK) { michael@0: LOG("inflateInit failed: %s", zStream.msg); michael@0: return false; michael@0: } michael@0: if (dictionary && inflateSetDictionary(&zStream, dictionary, michael@0: dictionary.numElements()) != Z_OK) { michael@0: LOG("inflateSetDictionary failed: %s", zStream.msg); michael@0: return false; michael@0: } michael@0: if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH) michael@0: != (length == chunkLen) ? Z_STREAM_END : Z_OK) { michael@0: LOG("inflate failed: %s", zStream.msg); michael@0: return false; michael@0: } michael@0: if (inflateEnd(&zStream) != Z_OK) { michael@0: LOG("inflateEnd failed: %s", zStream.msg); michael@0: return false; michael@0: } michael@0: if (filter) michael@0: filter(chunk * chunkSize, UNFILTER, (unsigned char *)where, chunkLen); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* Branch/Call/Jump conversion filter for Thumb, derived from xz-utils michael@0: * by Igor Pavlov and Lasse Collin, published in the public domain */ michael@0: static void michael@0: BCJ_Thumb_filter(off_t offset, SeekableZStream::FilterDirection dir, michael@0: unsigned char *buf, size_t size) michael@0: { michael@0: size_t i; michael@0: for (i = 0; i + 4 <= size; i += 2) { michael@0: if ((buf[i + 1] & 0xf8) == 0xf0 && (buf[i + 3] & 0xf8) == 0xf8) { michael@0: uint32_t src = (buf[i] << 11) michael@0: | ((buf[i + 1] & 0x07) << 19) michael@0: | buf[i + 2] michael@0: | ((buf[i + 3] & 0x07) << 8); michael@0: src <<= 1; michael@0: uint32_t dest; michael@0: if (dir == SeekableZStream::FILTER) michael@0: dest = offset + (uint32_t)(i) + 4 + src; michael@0: else michael@0: dest = src - (offset + (uint32_t)(i) + 4); michael@0: michael@0: dest >>= 1; michael@0: buf[i] = dest >> 11; michael@0: buf[i + 1] = 0xf0 | ((dest >> 19) & 0x07); michael@0: buf[i + 2] = dest; michael@0: buf[i + 3] = 0xf8 | ((dest >> 8) & 0x07); michael@0: i += 2; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Branch/Call/Jump conversion filter for ARM, derived from xz-utils michael@0: * by Igor Pavlov and Lasse Collin, published in the public domain */ michael@0: static void michael@0: BCJ_ARM_filter(off_t offset, SeekableZStream::FilterDirection dir, michael@0: unsigned char *buf, size_t size) michael@0: { michael@0: size_t i; michael@0: for (i = 0; i + 4 <= size; i += 4) { michael@0: if (buf[i + 3] == 0xeb) { michael@0: uint32_t src = buf[i] michael@0: | (buf[i + 1] << 8) michael@0: | (buf[i + 2] << 16); michael@0: src <<= 2; michael@0: uint32_t dest; michael@0: if (dir == SeekableZStream::FILTER) michael@0: dest = offset + (uint32_t)(i) + 8 + src; michael@0: else michael@0: dest = src - (offset + (uint32_t)(i) + 8); michael@0: michael@0: dest >>= 2; michael@0: buf[i] = dest; michael@0: buf[i + 1] = dest >> 8; michael@0: buf[i + 2] = dest >> 16; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Branch/Call/Jump conversion filter for x86, derived from xz-utils michael@0: * by Igor Pavlov and Lasse Collin, published in the public domain */ michael@0: michael@0: #define Test86MSByte(b) ((b) == 0 || (b) == 0xff) michael@0: michael@0: static void michael@0: BCJ_X86_filter(off_t offset, SeekableZStream::FilterDirection dir, michael@0: unsigned char *buf, size_t size) michael@0: { michael@0: static const bool MASK_TO_ALLOWED_STATUS[8] = michael@0: { true, true, true, false, true, false, false, false }; michael@0: michael@0: static const uint32_t MASK_TO_BIT_NUMBER[8] = michael@0: { 0, 1, 2, 2, 3, 3, 3, 3 }; michael@0: michael@0: uint32_t prev_mask = 0; michael@0: uint32_t prev_pos = 0; michael@0: michael@0: for (size_t i = 0; i + 5 <= size;) { michael@0: uint8_t b = buf[i]; michael@0: if (b != 0xe8 && b != 0xe9) { michael@0: ++i; michael@0: continue; michael@0: } michael@0: michael@0: const uint32_t off = offset + (uint32_t)(i) - prev_pos; michael@0: prev_pos = offset + (uint32_t)(i); michael@0: michael@0: if (off > 5) { michael@0: prev_mask = 0; michael@0: } else { michael@0: for (uint32_t i = 0; i < off; ++i) { michael@0: prev_mask &= 0x77; michael@0: prev_mask <<= 1; michael@0: } michael@0: } michael@0: michael@0: b = buf[i + 4]; michael@0: michael@0: if (Test86MSByte(b) && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7] michael@0: && (prev_mask >> 1) < 0x10) { michael@0: michael@0: uint32_t src = ((uint32_t)(b) << 24) michael@0: | ((uint32_t)(buf[i + 3]) << 16) michael@0: | ((uint32_t)(buf[i + 2]) << 8) michael@0: | (buf[i + 1]); michael@0: michael@0: uint32_t dest; michael@0: while (true) { michael@0: if (dir == SeekableZStream::FILTER) michael@0: dest = src + (offset + (uint32_t)(i) + 5); michael@0: else michael@0: dest = src - (offset + (uint32_t)(i) + 5); michael@0: michael@0: if (prev_mask == 0) michael@0: break; michael@0: michael@0: const uint32_t i = MASK_TO_BIT_NUMBER[prev_mask >> 1]; michael@0: michael@0: b = (uint8_t)(dest >> (24 - i * 8)); michael@0: michael@0: if (!Test86MSByte(b)) michael@0: break; michael@0: michael@0: src = dest ^ ((1 << (32 - i * 8)) - 1); michael@0: } michael@0: michael@0: buf[i + 4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); michael@0: buf[i + 3] = (uint8_t)(dest >> 16); michael@0: buf[i + 2] = (uint8_t)(dest >> 8); michael@0: buf[i + 1] = (uint8_t)(dest); michael@0: i += 5; michael@0: prev_mask = 0; michael@0: michael@0: } else { michael@0: ++i; michael@0: prev_mask |= 1; michael@0: if (Test86MSByte(b)) michael@0: prev_mask |= 0x10; michael@0: } michael@0: } michael@0: } michael@0: michael@0: SeekableZStream::ZStreamFilter michael@0: SeekableZStream::GetFilter(SeekableZStream::FilterId id) michael@0: { michael@0: switch (id) { michael@0: case BCJ_THUMB: michael@0: return BCJ_Thumb_filter; michael@0: case BCJ_ARM: michael@0: return BCJ_ARM_filter; michael@0: case BCJ_X86: michael@0: return BCJ_X86_filter; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: return nullptr; michael@0: }