summaryrefslogtreecommitdiffstats
path: root/xpcom/io/SnappyFrameUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/io/SnappyFrameUtils.cpp')
-rw-r--r--xpcom/io/SnappyFrameUtils.cpp258
1 files changed, 258 insertions, 0 deletions
diff --git a/xpcom/io/SnappyFrameUtils.cpp b/xpcom/io/SnappyFrameUtils.cpp
new file mode 100644
index 000000000..97883a362
--- /dev/null
+++ b/xpcom/io/SnappyFrameUtils.cpp
@@ -0,0 +1,258 @@
+/* -*- 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