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