1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "mozilla/SnappyFrameUtils.h"
8 
9 #include "crc32c.h"
10 #include "mozilla/EndianUtils.h"
11 #include "nsDebug.h"
12 #include "snappy/snappy.h"
13 
14 namespace {
15 
16 using mozilla::NativeEndian;
17 using mozilla::detail::SnappyFrameUtils;
18 
ReadChunkType(uint8_t aByte)19 SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte) {
20   if (aByte == 0xff) {
21     return SnappyFrameUtils::StreamIdentifier;
22   } else if (aByte == 0x00) {
23     return SnappyFrameUtils::CompressedData;
24   } else if (aByte == 0x01) {
25     return SnappyFrameUtils::UncompressedData;
26   } else if (aByte == 0xfe) {
27     return SnappyFrameUtils::Padding;
28   }
29 
30   return SnappyFrameUtils::Reserved;
31 }
32 
WriteChunkType(char * aDest,SnappyFrameUtils::ChunkType aType)33 void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType) {
34   unsigned char* dest = reinterpret_cast<unsigned char*>(aDest);
35   if (aType == SnappyFrameUtils::StreamIdentifier) {
36     *dest = 0xff;
37   } else if (aType == SnappyFrameUtils::CompressedData) {
38     *dest = 0x00;
39   } else if (aType == SnappyFrameUtils::UncompressedData) {
40     *dest = 0x01;
41   } else if (aType == SnappyFrameUtils::Padding) {
42     *dest = 0xfe;
43   } else {
44     *dest = 0x02;
45   }
46 }
47 
WriteUInt24(char * aBuf,uint32_t aVal)48 void WriteUInt24(char* aBuf, uint32_t aVal) {
49   MOZ_ASSERT(!(aVal & 0xff000000));
50   uint32_t tmp = NativeEndian::swapToLittleEndian(aVal);
51   memcpy(aBuf, &tmp, 3);
52 }
53 
ReadUInt24(const char * aBuf)54 uint32_t ReadUInt24(const char* aBuf) {
55   uint32_t val = 0;
56   memcpy(&val, aBuf, 3);
57   return NativeEndian::swapFromLittleEndian(val);
58 }
59 
60 // This mask is explicitly defined in the snappy framing_format.txt file.
MaskChecksum(uint32_t aValue)61 uint32_t MaskChecksum(uint32_t aValue) {
62   return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8;
63 }
64 
65 }  // namespace
66 
67 namespace mozilla {
68 namespace detail {
69 
70 using mozilla::LittleEndian;
71 
72 // static
WriteStreamIdentifier(char * aDest,size_t aDestLength,size_t * aBytesWrittenOut)73 nsresult SnappyFrameUtils::WriteStreamIdentifier(char* aDest,
74                                                  size_t aDestLength,
75                                                  size_t* aBytesWrittenOut) {
76   if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) {
77     return NS_ERROR_NOT_AVAILABLE;
78   }
79 
80   WriteChunkType(aDest, StreamIdentifier);
81   aDest[1] = 0x06;  // Data length
82   aDest[2] = 0x00;
83   aDest[3] = 0x00;
84   aDest[4] = 0x73;  // "sNaPpY"
85   aDest[5] = 0x4e;
86   aDest[6] = 0x61;
87   aDest[7] = 0x50;
88   aDest[8] = 0x70;
89   aDest[9] = 0x59;
90 
91   static_assert(kHeaderLength + kStreamIdentifierDataLength == 10,
92                 "StreamIdentifier chunk should be exactly 10 bytes long");
93   *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength;
94 
95   return NS_OK;
96 }
97 
98 // static
WriteCompressedData(char * aDest,size_t aDestLength,const char * aData,size_t aDataLength,size_t * aBytesWrittenOut)99 nsresult SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength,
100                                                const char* aData,
101                                                size_t aDataLength,
102                                                size_t* aBytesWrittenOut) {
103   *aBytesWrittenOut = 0;
104 
105   size_t neededLength = MaxCompressedBufferLength(aDataLength);
106   if (NS_WARN_IF(aDestLength < neededLength)) {
107     return NS_ERROR_NOT_AVAILABLE;
108   }
109 
110   size_t offset = 0;
111 
112   WriteChunkType(aDest, CompressedData);
113   offset += kChunkTypeLength;
114 
115   // Skip length for now and write it out after we know the compressed length.
116   size_t lengthOffset = offset;
117   offset += kChunkLengthLength;
118 
119   uint32_t crc = ComputeCrc32c(
120       ~0, reinterpret_cast<const unsigned char*>(aData), aDataLength);
121   uint32_t maskedCrc = MaskChecksum(crc);
122   LittleEndian::writeUint32(aDest + offset, maskedCrc);
123   offset += kCRCLength;
124 
125   size_t compressedLength;
126   snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength);
127 
128   // Go back and write the data length.
129   size_t dataLength = compressedLength + kCRCLength;
130   WriteUInt24(aDest + lengthOffset, dataLength);
131 
132   *aBytesWrittenOut = kHeaderLength + dataLength;
133 
134   return NS_OK;
135 }
136 
137 // static
ParseHeader(const char * aSource,size_t aSourceLength,ChunkType * aTypeOut,size_t * aDataLengthOut)138 nsresult SnappyFrameUtils::ParseHeader(const char* aSource,
139                                        size_t aSourceLength,
140                                        ChunkType* aTypeOut,
141                                        size_t* aDataLengthOut) {
142   if (NS_WARN_IF(aSourceLength < kHeaderLength)) {
143     return NS_ERROR_NOT_AVAILABLE;
144   }
145 
146   *aTypeOut = ReadChunkType(aSource[0]);
147   *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength);
148 
149   return NS_OK;
150 }
151 
152 // static
ParseData(char * aDest,size_t aDestLength,ChunkType aType,const char * aData,size_t aDataLength,size_t * aBytesWrittenOut,size_t * aBytesReadOut)153 nsresult SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength,
154                                      ChunkType aType, const char* aData,
155                                      size_t aDataLength,
156                                      size_t* aBytesWrittenOut,
157                                      size_t* aBytesReadOut) {
158   switch (aType) {
159     case StreamIdentifier:
160       return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength,
161                                    aBytesWrittenOut, aBytesReadOut);
162 
163     case CompressedData:
164       return ParseCompressedData(aDest, aDestLength, aData, aDataLength,
165                                  aBytesWrittenOut, aBytesReadOut);
166 
167     // TODO: support other snappy chunk types
168     default:
169       MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type.");
170       return NS_ERROR_NOT_IMPLEMENTED;
171   }
172 }
173 
174 // static
ParseStreamIdentifier(char *,size_t,const char * aData,size_t aDataLength,size_t * aBytesWrittenOut,size_t * aBytesReadOut)175 nsresult SnappyFrameUtils::ParseStreamIdentifier(char*, size_t,
176                                                  const char* aData,
177                                                  size_t aDataLength,
178                                                  size_t* aBytesWrittenOut,
179                                                  size_t* aBytesReadOut) {
180   *aBytesWrittenOut = 0;
181   *aBytesReadOut = 0;
182   if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength ||
183                  aData[0] != 0x73 || aData[1] != 0x4e || aData[2] != 0x61 ||
184                  aData[3] != 0x50 || aData[4] != 0x70 || aData[5] != 0x59)) {
185     return NS_ERROR_CORRUPTED_CONTENT;
186   }
187   *aBytesReadOut = aDataLength;
188   return NS_OK;
189 }
190 
191 // static
ParseCompressedData(char * aDest,size_t aDestLength,const char * aData,size_t aDataLength,size_t * aBytesWrittenOut,size_t * aBytesReadOut)192 nsresult SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength,
193                                                const char* aData,
194                                                size_t aDataLength,
195                                                size_t* aBytesWrittenOut,
196                                                size_t* aBytesReadOut) {
197   *aBytesWrittenOut = 0;
198   *aBytesReadOut = 0;
199   size_t offset = 0;
200 
201   uint32_t readCrc = LittleEndian::readUint32(aData + offset);
202   offset += kCRCLength;
203 
204   size_t uncompressedLength;
205   if (NS_WARN_IF(!snappy::GetUncompressedLength(
206           aData + offset, aDataLength - offset, &uncompressedLength))) {
207     return NS_ERROR_CORRUPTED_CONTENT;
208   }
209 
210   if (NS_WARN_IF(aDestLength < uncompressedLength)) {
211     return NS_ERROR_NOT_AVAILABLE;
212   }
213 
214   if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset,
215                                         aDest))) {
216     return NS_ERROR_CORRUPTED_CONTENT;
217   }
218 
219   uint32_t crc = ComputeCrc32c(
220       ~0, reinterpret_cast<const unsigned char*>(aDest), uncompressedLength);
221   uint32_t maskedCrc = MaskChecksum(crc);
222   if (NS_WARN_IF(readCrc != maskedCrc)) {
223     return NS_ERROR_CORRUPTED_CONTENT;
224   }
225 
226   *aBytesWrittenOut = uncompressedLength;
227   *aBytesReadOut = aDataLength;
228 
229   return NS_OK;
230 }
231 
232 // static
MaxCompressedBufferLength(size_t aSourceLength)233 size_t SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength) {
234   size_t neededLength = kHeaderLength;
235   neededLength += kCRCLength;
236   neededLength += snappy::MaxCompressedLength(aSourceLength);
237   return neededLength;
238 }
239 
240 }  // namespace detail
241 }  // namespace mozilla
242