/src/mozilla-central/xpcom/io/SnappyUncompressInputStream.cpp
Line | Count | Source (jump to first uncovered line) |
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/SnappyUncompressInputStream.h" |
8 | | |
9 | | #include <algorithm> |
10 | | #include "nsIAsyncInputStream.h" |
11 | | #include "nsStreamUtils.h" |
12 | | #include "snappy/snappy.h" |
13 | | |
14 | | namespace mozilla { |
15 | | |
16 | | NS_IMPL_ISUPPORTS(SnappyUncompressInputStream, |
17 | | nsIInputStream); |
18 | | |
19 | | // Putting kCompressedBufferLength inside a function avoids a static |
20 | | // constructor. |
21 | | static size_t CompressedBufferLength() |
22 | 0 | { |
23 | 0 | static size_t kCompressedBufferLength = |
24 | 0 | detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize); |
25 | 0 |
|
26 | 0 | MOZ_ASSERT(kCompressedBufferLength > 0); |
27 | 0 | return kCompressedBufferLength; |
28 | 0 | } |
29 | | |
30 | | SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream) |
31 | | : mBaseStream(aBaseStream) |
32 | | , mUncompressedBytes(0) |
33 | | , mNextByte(0) |
34 | | , mNextChunkType(Unknown) |
35 | | , mNextChunkDataLength(0) |
36 | | , mNeedFirstStreamIdentifier(true) |
37 | 0 | { |
38 | 0 | // This implementation only supports sync base streams. Verify this in debug |
39 | 0 | // builds. Note, this is a bit complicated because the streams we support |
40 | 0 | // advertise different capabilities: |
41 | 0 | // - nsFileInputStream - blocking and sync |
42 | 0 | // - nsStringInputStream - non-blocking and sync |
43 | 0 | // - nsPipeInputStream - can be blocking, but provides async interface |
44 | | #ifdef DEBUG |
45 | | bool baseNonBlocking; |
46 | | nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); |
47 | | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
48 | | if (baseNonBlocking) { |
49 | | nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream); |
50 | | MOZ_ASSERT(!async); |
51 | | } |
52 | | #endif |
53 | | } |
54 | | |
55 | | NS_IMETHODIMP |
56 | | SnappyUncompressInputStream::Close() |
57 | 0 | { |
58 | 0 | if (!mBaseStream) { |
59 | 0 | return NS_OK; |
60 | 0 | } |
61 | 0 | |
62 | 0 | mBaseStream->Close(); |
63 | 0 | mBaseStream = nullptr; |
64 | 0 |
|
65 | 0 | mUncompressedBuffer = nullptr; |
66 | 0 | mCompressedBuffer = nullptr; |
67 | 0 |
|
68 | 0 | return NS_OK; |
69 | 0 | } |
70 | | |
71 | | NS_IMETHODIMP |
72 | | SnappyUncompressInputStream::Available(uint64_t* aLengthOut) |
73 | 0 | { |
74 | 0 | if (!mBaseStream) { |
75 | 0 | return NS_BASE_STREAM_CLOSED; |
76 | 0 | } |
77 | 0 | |
78 | 0 | // If we have uncompressed bytes, then we are done. |
79 | 0 | *aLengthOut = UncompressedLength(); |
80 | 0 | if (*aLengthOut > 0) { |
81 | 0 | return NS_OK; |
82 | 0 | } |
83 | 0 | |
84 | 0 | // Otherwise, attempt to uncompress bytes until we get something or the |
85 | 0 | // underlying stream is drained. We loop here because some chunks can |
86 | 0 | // be StreamIdentifiers, padding, etc with no data. |
87 | 0 | uint32_t bytesRead; |
88 | 0 | do { |
89 | 0 | nsresult rv = ParseNextChunk(&bytesRead); |
90 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
91 | 0 | *aLengthOut = UncompressedLength(); |
92 | 0 | } while(*aLengthOut == 0 && bytesRead); |
93 | 0 |
|
94 | 0 | return NS_OK; |
95 | 0 | } |
96 | | |
97 | | NS_IMETHODIMP |
98 | | SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount, |
99 | | uint32_t* aBytesReadOut) |
100 | 0 | { |
101 | 0 | return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut); |
102 | 0 | } |
103 | | |
104 | | NS_IMETHODIMP |
105 | | SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter, |
106 | | void* aClosure, uint32_t aCount, |
107 | | uint32_t* aBytesReadOut) |
108 | 0 | { |
109 | 0 | *aBytesReadOut = 0; |
110 | 0 |
|
111 | 0 | if (!mBaseStream) { |
112 | 0 | return NS_BASE_STREAM_CLOSED; |
113 | 0 | } |
114 | 0 | |
115 | 0 | nsresult rv; |
116 | 0 |
|
117 | 0 | // Do not try to use the base stream's ReadSegements here. Its very |
118 | 0 | // unlikely we will get a single buffer that contains all of the compressed |
119 | 0 | // data and therefore would have to copy into our own buffer anyways. |
120 | 0 | // Instead, focus on making efficient use of the Read() interface. |
121 | 0 |
|
122 | 0 | while (aCount > 0) { |
123 | 0 | // We have some decompressed data in our buffer. Provide it to the |
124 | 0 | // callers writer function. |
125 | 0 | if (mUncompressedBytes > 0) { |
126 | 0 | MOZ_ASSERT(mUncompressedBuffer); |
127 | 0 | uint32_t remaining = UncompressedLength(); |
128 | 0 | uint32_t numToWrite = std::min(aCount, remaining); |
129 | 0 | uint32_t numWritten; |
130 | 0 | rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut, |
131 | 0 | numToWrite, &numWritten); |
132 | 0 |
|
133 | 0 | // As defined in nsIInputputStream.idl, do not pass writer func errors. |
134 | 0 | if (NS_FAILED(rv)) { |
135 | 0 | return NS_OK; |
136 | 0 | } |
137 | 0 | |
138 | 0 | // End-of-file |
139 | 0 | if (numWritten == 0) { |
140 | 0 | return NS_OK; |
141 | 0 | } |
142 | 0 | |
143 | 0 | *aBytesReadOut += numWritten; |
144 | 0 | mNextByte += numWritten; |
145 | 0 | MOZ_ASSERT(mNextByte <= mUncompressedBytes); |
146 | 0 |
|
147 | 0 | if (mNextByte == mUncompressedBytes) { |
148 | 0 | mNextByte = 0; |
149 | 0 | mUncompressedBytes = 0; |
150 | 0 | } |
151 | 0 |
|
152 | 0 | aCount -= numWritten; |
153 | 0 |
|
154 | 0 | continue; |
155 | 0 | } |
156 | 0 |
|
157 | 0 | // Otherwise uncompress the next chunk and loop. Any resulting data |
158 | 0 | // will set mUncompressedBytes which we check at the top of the loop. |
159 | 0 | uint32_t bytesRead; |
160 | 0 | rv = ParseNextChunk(&bytesRead); |
161 | 0 | if (NS_FAILED(rv)) { return rv; } |
162 | 0 | |
163 | 0 | // If we couldn't read anything and there is no more data to provide |
164 | 0 | // to the caller, then this is eof. |
165 | 0 | if (bytesRead == 0 && mUncompressedBytes == 0) { |
166 | 0 | return NS_OK; |
167 | 0 | } |
168 | 0 | } |
169 | 0 |
|
170 | 0 | return NS_OK; |
171 | 0 | } |
172 | | |
173 | | NS_IMETHODIMP |
174 | | SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut) |
175 | 0 | { |
176 | 0 | *aNonBlockingOut = false; |
177 | 0 | return NS_OK; |
178 | 0 | } |
179 | | |
180 | | SnappyUncompressInputStream::~SnappyUncompressInputStream() |
181 | 0 | { |
182 | 0 | Close(); |
183 | 0 | } |
184 | | |
185 | | nsresult |
186 | | SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut) |
187 | 0 | { |
188 | 0 | // There must not be any uncompressed data already in mUncompressedBuffer. |
189 | 0 | MOZ_ASSERT(mUncompressedBytes == 0); |
190 | 0 | MOZ_ASSERT(mNextByte == 0); |
191 | 0 |
|
192 | 0 | nsresult rv; |
193 | 0 | *aBytesReadOut = 0; |
194 | 0 |
|
195 | 0 | // Lazily create our two buffers so we can report OOM during stream |
196 | 0 | // operation. These allocations only happens once. The buffers are reused |
197 | 0 | // until the stream is closed. |
198 | 0 | if (!mUncompressedBuffer) { |
199 | 0 | mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]); |
200 | 0 | if (NS_WARN_IF(!mUncompressedBuffer)) { |
201 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
202 | 0 | } |
203 | 0 | } |
204 | 0 | |
205 | 0 | if (!mCompressedBuffer) { |
206 | 0 | mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]); |
207 | 0 | if (NS_WARN_IF(!mCompressedBuffer)) { |
208 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
209 | 0 | } |
210 | 0 | } |
211 | 0 | |
212 | 0 | // We have no decompressed data and we also have not seen the start of stream |
213 | 0 | // yet. Read and validate the StreamIdentifier chunk. Also read the next |
214 | 0 | // header to determine the size of the first real data chunk. |
215 | 0 | if (mNeedFirstStreamIdentifier) { |
216 | 0 | const uint32_t firstReadLength = kHeaderLength + |
217 | 0 | kStreamIdentifierDataLength + |
218 | 0 | kHeaderLength; |
219 | 0 | MOZ_ASSERT(firstReadLength <= CompressedBufferLength()); |
220 | 0 |
|
221 | 0 | rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength, |
222 | 0 | aBytesReadOut); |
223 | 0 | if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } |
224 | 0 | |
225 | 0 | rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, |
226 | 0 | &mNextChunkType, &mNextChunkDataLength); |
227 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
228 | 0 | if (NS_WARN_IF(mNextChunkType != StreamIdentifier || |
229 | 0 | mNextChunkDataLength != kStreamIdentifierDataLength)) { |
230 | 0 | return NS_ERROR_CORRUPTED_CONTENT; |
231 | 0 | } |
232 | 0 | size_t offset = kHeaderLength; |
233 | 0 |
|
234 | 0 | mNeedFirstStreamIdentifier = false; |
235 | 0 |
|
236 | 0 | size_t numRead; |
237 | 0 | size_t numWritten; |
238 | 0 | rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, |
239 | 0 | &mCompressedBuffer[offset], |
240 | 0 | mNextChunkDataLength, &numWritten, &numRead); |
241 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
242 | 0 | MOZ_ASSERT(numWritten == 0); |
243 | 0 | MOZ_ASSERT(numRead == mNextChunkDataLength); |
244 | 0 | offset += numRead; |
245 | 0 |
|
246 | 0 | rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset, |
247 | 0 | &mNextChunkType, &mNextChunkDataLength); |
248 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
249 | 0 | |
250 | 0 | return NS_OK; |
251 | 0 | } |
252 | 0 | |
253 | 0 | // We have no compressed data and we don't know how big the next chunk is. |
254 | 0 | // This happens when we get an EOF pause in the middle of a stream and also |
255 | 0 | // at the end of the stream. Simply read the next header and return. The |
256 | 0 | // chunk body will be read on the next entry into this method. |
257 | 0 | if (mNextChunkType == Unknown) { |
258 | 0 | rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength, |
259 | 0 | aBytesReadOut); |
260 | 0 | if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } |
261 | 0 | |
262 | 0 | rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, |
263 | 0 | &mNextChunkType, &mNextChunkDataLength); |
264 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
265 | 0 | |
266 | 0 | return NS_OK; |
267 | 0 | } |
268 | 0 | |
269 | 0 | // We have no decompressed data, but we do know the size of the next chunk. |
270 | 0 | // Read at least that much from the base stream. |
271 | 0 | uint32_t readLength = mNextChunkDataLength; |
272 | 0 | MOZ_ASSERT(readLength <= CompressedBufferLength()); |
273 | 0 |
|
274 | 0 | // However, if there is enough data in the base stream, also read the next |
275 | 0 | // chunk header. This helps optimize the stream by avoiding many small reads. |
276 | 0 | uint64_t avail; |
277 | 0 | rv = mBaseStream->Available(&avail); |
278 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
279 | 0 | if (avail >= (readLength + kHeaderLength)) { |
280 | 0 | readLength += kHeaderLength; |
281 | 0 | MOZ_ASSERT(readLength <= CompressedBufferLength()); |
282 | 0 | } |
283 | 0 |
|
284 | 0 | rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength, |
285 | 0 | aBytesReadOut); |
286 | 0 | if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } |
287 | 0 | |
288 | 0 | size_t numRead; |
289 | 0 | size_t numWritten; |
290 | 0 | rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, |
291 | 0 | mCompressedBuffer.get(), mNextChunkDataLength, |
292 | 0 | &numWritten, &numRead); |
293 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
294 | 0 | MOZ_ASSERT(numRead == mNextChunkDataLength); |
295 | 0 |
|
296 | 0 | mUncompressedBytes = numWritten; |
297 | 0 |
|
298 | 0 | // If we were unable to directly read the next chunk header, then clear |
299 | 0 | // our internal state. We will have to perform a small read to get the |
300 | 0 | // header the next time we enter this method. |
301 | 0 | if (*aBytesReadOut <= mNextChunkDataLength) { |
302 | 0 | mNextChunkType = Unknown; |
303 | 0 | mNextChunkDataLength = 0; |
304 | 0 | return NS_OK; |
305 | 0 | } |
306 | 0 | |
307 | 0 | // We got the next chunk header. Parse it so that we are ready to for the |
308 | 0 | // next call into this method. |
309 | 0 | rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead, |
310 | 0 | &mNextChunkType, &mNextChunkDataLength); |
311 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
312 | 0 | |
313 | 0 | return NS_OK; |
314 | 0 | } |
315 | | |
316 | | nsresult |
317 | | SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount, |
318 | | uint32_t aMinValidCount, |
319 | | uint32_t* aBytesReadOut) |
320 | 0 | { |
321 | 0 | MOZ_ASSERT(aCount >= aMinValidCount); |
322 | 0 |
|
323 | 0 | *aBytesReadOut = 0; |
324 | 0 |
|
325 | 0 | if (!mBaseStream) { |
326 | 0 | return NS_BASE_STREAM_CLOSED; |
327 | 0 | } |
328 | 0 | |
329 | 0 | uint32_t offset = 0; |
330 | 0 | while (aCount > 0) { |
331 | 0 | uint32_t bytesRead = 0; |
332 | 0 | nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead); |
333 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } |
334 | 0 | |
335 | 0 | // EOF, but don't immediately return. We need to validate min read bytes |
336 | 0 | // below. |
337 | 0 | if (bytesRead == 0) { |
338 | 0 | break; |
339 | 0 | } |
340 | 0 | |
341 | 0 | *aBytesReadOut += bytesRead; |
342 | 0 | offset += bytesRead; |
343 | 0 | aCount -= bytesRead; |
344 | 0 | } |
345 | 0 |
|
346 | 0 | // Reading zero bytes is not an error. Its the expected EOF condition. |
347 | 0 | // Only compare to the minimum valid count if we read at least one byte. |
348 | 0 | if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) { |
349 | 0 | return NS_ERROR_CORRUPTED_CONTENT; |
350 | 0 | } |
351 | 0 | |
352 | 0 | return NS_OK; |
353 | 0 | } |
354 | | |
355 | | size_t |
356 | | SnappyUncompressInputStream::UncompressedLength() const |
357 | 0 | { |
358 | 0 | MOZ_ASSERT(mNextByte <= mUncompressedBytes); |
359 | 0 | return mUncompressedBytes - mNextByte; |
360 | 0 | } |
361 | | |
362 | | } // namespace mozilla |