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