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