diff options
Diffstat (limited to 'mozglue/linker/XZStream.cpp')
-rw-r--r-- | mozglue/linker/XZStream.cpp | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/mozglue/linker/XZStream.cpp b/mozglue/linker/XZStream.cpp new file mode 100644 index 000000000..bd71655f5 --- /dev/null +++ b/mozglue/linker/XZStream.cpp @@ -0,0 +1,213 @@ +#include "XZStream.h" + +#include <algorithm> +#include "mozilla/Assertions.h" +#include "Logging.h" + +// LZMA dictionary size, should have a minimum size for the given compression +// rate, see XZ Utils docs for details. +static const uint32_t kDictSize = 1 << 24; + +static const size_t kFooterSize = 12; + +// Parses a variable-length integer (VLI), +// see http://tukaani.org/xz/xz-file-format.txt for details. +static size_t +ParseVarLenInt(const uint8_t* aBuf, size_t aBufSize, uint64_t* aValue) +{ + if (!aBufSize) { + return 0; + } + aBufSize = std::min(9u, aBufSize); + + *aValue = aBuf[0] & 0x7F; + size_t i = 0; + + while (aBuf[i++] & 0x80) { + if (i >= aBufSize || aBuf[i] == 0x0) { + return 0; + } + *aValue |= static_cast<uint64_t>(aBuf[i] & 0x7F) << (i * 7); + } + return i; +} + +/* static */ bool +XZStream::IsXZ(const void* aBuf, size_t aBufSize) +{ + static const uint8_t kXzMagic[] = {0xfd, '7', 'z', 'X', 'Z', 0x0}; + MOZ_ASSERT(aBuf); + return aBufSize > sizeof(kXzMagic) && + !memcmp(reinterpret_cast<const void*>(kXzMagic), aBuf, sizeof(kXzMagic)); +} + +XZStream::XZStream(const void* aInBuf, size_t aInSize) + : mInBuf(static_cast<const uint8_t*>(aInBuf)) + , mUncompSize(0) + , mDec(nullptr) +{ + mBuffers.in = mInBuf; + mBuffers.in_pos = 0; + mBuffers.in_size = aInSize; +} + +XZStream::~XZStream() +{ + xz_dec_end(mDec); +} + +bool +XZStream::Init() +{ +#ifdef XZ_USE_CRC64 + xz_crc64_init(); +#endif + xz_crc32_init(); + + mDec = xz_dec_init(XZ_DYNALLOC, kDictSize); + + if (!mDec) { + return false; + } + + mUncompSize = ParseUncompressedSize(); + + return true; +} + +size_t +XZStream::Decode(void* aOutBuf, size_t aOutSize) +{ + if (!mDec) { + return 0; + } + + mBuffers.out = static_cast<uint8_t*>(aOutBuf); + mBuffers.out_pos = 0; + mBuffers.out_size = aOutSize; + + while (mBuffers.in_pos < mBuffers.in_size && + mBuffers.out_pos < mBuffers.out_size) { + const xz_ret ret = xz_dec_run(mDec, &mBuffers); + + switch (ret) { + case XZ_STREAM_END: + // Stream ended, the next loop iteration should terminate. + MOZ_ASSERT(mBuffers.in_pos == mBuffers.in_size); + MOZ_FALLTHROUGH; +#ifdef XZ_DEC_ANY_CHECK + case XZ_UNSUPPORTED_CHECK: + // Ignore unsupported check. + MOZ_FALLTHROUGH; +#endif + case XZ_OK: + // Chunk decoded, proceed. + break; + + case XZ_MEM_ERROR: + ERROR("XZ decoding: memory allocation failed"); + return 0; + + case XZ_MEMLIMIT_ERROR: + ERROR("XZ decoding: memory usage limit reached"); + return 0; + + case XZ_FORMAT_ERROR: + ERROR("XZ decoding: invalid stream format"); + return 0; + + case XZ_OPTIONS_ERROR: + ERROR("XZ decoding: unsupported header options"); + return 0; + + case XZ_DATA_ERROR: + MOZ_FALLTHROUGH; + case XZ_BUF_ERROR: + ERROR("XZ decoding: corrupt input stream"); + return 0; + + default: + MOZ_ASSERT_UNREACHABLE("XZ decoding: unknown error condition"); + return 0; + } + } + return mBuffers.out_pos; +} + +size_t +XZStream::RemainingInput() const +{ + return mBuffers.in_size - mBuffers.in_pos; +} + +size_t +XZStream::Size() const +{ + return mBuffers.in_size; +} + +size_t +XZStream::UncompressedSize() const +{ + return mUncompSize; +} + +size_t +XZStream::ParseIndexSize() const +{ + static const uint8_t kFooterMagic[] = {'Y', 'Z'}; + + const uint8_t* footer = mInBuf + mBuffers.in_size - kFooterSize; + // The magic bytes are at the end of the footer. + if (memcmp(reinterpret_cast<const void*>(kFooterMagic), + footer + kFooterSize - sizeof(kFooterMagic), + sizeof(kFooterMagic))) { + // Not a valid footer at stream end. + return 0; + } + // Backward size is a 32 bit LE integer field positioned after the 32 bit CRC32 + // code. It encodes the index size as a multiple of 4 bytes with a minimum + // size of 4 bytes. + const uint32_t backwardSize = *(footer + 4); + return (backwardSize + 1) * 4; +} + +size_t +XZStream::ParseUncompressedSize() const +{ + static const uint8_t kIndexIndicator[] = {0x0}; + + const size_t indexSize = ParseIndexSize(); + if (!indexSize) { + return 0; + } + // The footer follows directly the index, so we can use it as a reference. + const uint8_t* end = mInBuf + mBuffers.in_size; + const uint8_t* index = end - kFooterSize - indexSize; + + // The index consists of a one byte indicator followed by a VLI field for the + // number of records (1 expected) followed by a list of records. One record + // contains a VLI field for unpadded size followed by a VLI field for + // uncompressed size. + if (memcmp(reinterpret_cast<const void*>(kIndexIndicator), + index, sizeof(kIndexIndicator))) { + // Not a valid index. + return 0; + } + + index += sizeof(kIndexIndicator); + uint64_t numRecords = 0; + index += ParseVarLenInt(index, end - index, &numRecords); + if (!numRecords) { + return 0; + } + uint64_t unpaddedSize = 0; + index += ParseVarLenInt(index, end - index, &unpaddedSize); + if (!unpaddedSize) { + return 0; + } + uint64_t uncompressedSize = 0; + index += ParseVarLenInt(index, end - index, &uncompressedSize); + + return uncompressedSize; +} |