/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/SnappyFrameUtils.h" #include "crc32c.h" #include "mozilla/EndianUtils.h" #include "nsDebug.h" #include "snappy/snappy.h" namespace { using mozilla::NativeEndian; using mozilla::detail::SnappyFrameUtils; SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte) { if (aByte == 0xff) { return SnappyFrameUtils::StreamIdentifier; } else if (aByte == 0x00) { return SnappyFrameUtils::CompressedData; } else if (aByte == 0x01) { return SnappyFrameUtils::UncompressedData; } else if (aByte == 0xfe) { return SnappyFrameUtils::Padding; } return SnappyFrameUtils::Reserved; } void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType) { unsigned char* dest = reinterpret_cast(aDest); if (aType == SnappyFrameUtils::StreamIdentifier) { *dest = 0xff; } else if (aType == SnappyFrameUtils::CompressedData) { *dest = 0x00; } else if (aType == SnappyFrameUtils::UncompressedData) { *dest = 0x01; } else if (aType == SnappyFrameUtils::Padding) { *dest = 0xfe; } else { *dest = 0x02; } } void WriteUInt24(char* aBuf, uint32_t aVal) { MOZ_ASSERT(!(aVal & 0xff000000)); uint32_t tmp = NativeEndian::swapToLittleEndian(aVal); memcpy(aBuf, &tmp, 3); } uint32_t ReadUInt24(const char* aBuf) { uint32_t val = 0; memcpy(&val, aBuf, 3); return NativeEndian::swapFromLittleEndian(val); } // This mask is explicitly defined in the snappy framing_format.txt file. uint32_t MaskChecksum(uint32_t aValue) { return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8; } } // namespace namespace mozilla { namespace detail { using mozilla::LittleEndian; // static nsresult SnappyFrameUtils::WriteStreamIdentifier(char* aDest, size_t aDestLength, size_t* aBytesWrittenOut) { if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) { return NS_ERROR_NOT_AVAILABLE; } WriteChunkType(aDest, StreamIdentifier); aDest[1] = 0x06; // Data length aDest[2] = 0x00; aDest[3] = 0x00; aDest[4] = 0x73; // "sNaPpY" aDest[5] = 0x4e; aDest[6] = 0x61; aDest[7] = 0x50; aDest[8] = 0x70; aDest[9] = 0x59; static_assert(kHeaderLength + kStreamIdentifierDataLength == 10, "StreamIdentifier chunk should be exactly 10 bytes long"); *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength; return NS_OK; } // static nsresult SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength, const char* aData, size_t aDataLength, size_t* aBytesWrittenOut) { *aBytesWrittenOut = 0; size_t neededLength = MaxCompressedBufferLength(aDataLength); if (NS_WARN_IF(aDestLength < neededLength)) { return NS_ERROR_NOT_AVAILABLE; } size_t offset = 0; WriteChunkType(aDest, CompressedData); offset += kChunkTypeLength; // Skip length for now and write it out after we know the compressed length. size_t lengthOffset = offset; offset += kChunkLengthLength; uint32_t crc = ComputeCrc32c( ~0, reinterpret_cast(aData), aDataLength); uint32_t maskedCrc = MaskChecksum(crc); LittleEndian::writeUint32(aDest + offset, maskedCrc); offset += kCRCLength; size_t compressedLength; snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength); // Go back and write the data length. size_t dataLength = compressedLength + kCRCLength; WriteUInt24(aDest + lengthOffset, dataLength); *aBytesWrittenOut = kHeaderLength + dataLength; return NS_OK; } // static nsresult SnappyFrameUtils::ParseHeader(const char* aSource, size_t aSourceLength, ChunkType* aTypeOut, size_t* aDataLengthOut) { if (NS_WARN_IF(aSourceLength < kHeaderLength)) { return NS_ERROR_NOT_AVAILABLE; } *aTypeOut = ReadChunkType(aSource[0]); *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength); return NS_OK; } // static nsresult SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength, ChunkType aType, const char* aData, size_t aDataLength, size_t* aBytesWrittenOut, size_t* aBytesReadOut) { switch (aType) { case StreamIdentifier: return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength, aBytesWrittenOut, aBytesReadOut); case CompressedData: return ParseCompressedData(aDest, aDestLength, aData, aDataLength, aBytesWrittenOut, aBytesReadOut); // TODO: support other snappy chunk types default: MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type."); return NS_ERROR_NOT_IMPLEMENTED; } } // static nsresult SnappyFrameUtils::ParseStreamIdentifier(char*, size_t, const char* aData, size_t aDataLength, size_t* aBytesWrittenOut, size_t* aBytesReadOut) { *aBytesWrittenOut = 0; *aBytesReadOut = 0; if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength || aData[0] != 0x73 || aData[1] != 0x4e || aData[2] != 0x61 || aData[3] != 0x50 || aData[4] != 0x70 || aData[5] != 0x59)) { return NS_ERROR_CORRUPTED_CONTENT; } *aBytesReadOut = aDataLength; return NS_OK; } // static nsresult SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength, const char* aData, size_t aDataLength, size_t* aBytesWrittenOut, size_t* aBytesReadOut) { *aBytesWrittenOut = 0; *aBytesReadOut = 0; size_t offset = 0; uint32_t readCrc = LittleEndian::readUint32(aData + offset); offset += kCRCLength; size_t uncompressedLength; if (NS_WARN_IF(!snappy::GetUncompressedLength( aData + offset, aDataLength - offset, &uncompressedLength))) { return NS_ERROR_CORRUPTED_CONTENT; } if (NS_WARN_IF(aDestLength < uncompressedLength)) { return NS_ERROR_NOT_AVAILABLE; } if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset, aDest))) { return NS_ERROR_CORRUPTED_CONTENT; } uint32_t crc = ComputeCrc32c( ~0, reinterpret_cast(aDest), uncompressedLength); uint32_t maskedCrc = MaskChecksum(crc); if (NS_WARN_IF(readCrc != maskedCrc)) { return NS_ERROR_CORRUPTED_CONTENT; } *aBytesWrittenOut = uncompressedLength; *aBytesReadOut = aDataLength; return NS_OK; } // static size_t SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength) { size_t neededLength = kHeaderLength; neededLength += kCRCLength; neededLength += snappy::MaxCompressedLength(aSourceLength); return neededLength; } } // namespace detail } // namespace mozilla