gecko-dev/xpcom/io/SnappyFrameUtils.cpp
2016-05-22 13:31:11 -07:00

259 lines
7.3 KiB
C++

/* -*- 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::detail::SnappyFrameUtils;
using mozilla::NativeEndian;
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<unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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