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/. */
7 #include "mozilla/SnappyUncompressInputStream.h"
10 #include "nsIAsyncInputStream.h"
11 #include "nsStreamUtils.h"
12 #include "snappy/snappy.h"
16 NS_IMPL_ISUPPORTS(SnappyUncompressInputStream
,
19 // Putting kCompressedBufferLength inside a function avoids a static
21 static size_t CompressedBufferLength()
23 static size_t kCompressedBufferLength
=
24 detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize
);
26 MOZ_ASSERT(kCompressedBufferLength
> 0);
27 return kCompressedBufferLength
;
30 SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream
* aBaseStream
)
31 : mBaseStream(aBaseStream
)
32 , mUncompressedBytes(0)
34 , mNextChunkType(Unknown
)
35 , mNextChunkDataLength(0)
36 , mNeedFirstStreamIdentifier(true)
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
46 nsresult rv
= mBaseStream
->IsNonBlocking(&baseNonBlocking
);
47 MOZ_ASSERT(NS_SUCCEEDED(rv
));
48 if (baseNonBlocking
) {
49 nsCOMPtr
<nsIAsyncInputStream
> async
= do_QueryInterface(mBaseStream
);
56 SnappyUncompressInputStream::Close()
63 mBaseStream
= nullptr;
65 mUncompressedBuffer
= nullptr;
66 mCompressedBuffer
= nullptr;
72 SnappyUncompressInputStream::Available(uint64_t* aLengthOut
)
75 return NS_BASE_STREAM_CLOSED
;
78 // If we have uncompressed bytes, then we are done.
79 *aLengthOut
= UncompressedLength();
80 if (*aLengthOut
> 0) {
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.
89 nsresult rv
= ParseNextChunk(&bytesRead
);
90 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
91 *aLengthOut
= UncompressedLength();
92 } while(*aLengthOut
== 0 && bytesRead
);
98 SnappyUncompressInputStream::Read(char* aBuf
, uint32_t aCount
,
99 uint32_t* aBytesReadOut
)
101 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, aBytesReadOut
);
105 SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter
,
106 void* aClosure
, uint32_t aCount
,
107 uint32_t* aBytesReadOut
)
112 return NS_BASE_STREAM_CLOSED
;
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.
123 // We have some decompressed data in our buffer. Provide it to the
124 // callers writer function.
125 if (mUncompressedBytes
> 0) {
126 MOZ_ASSERT(mUncompressedBuffer
);
127 uint32_t remaining
= UncompressedLength();
128 uint32_t numToWrite
= std::min(aCount
, remaining
);
130 rv
= aWriter(this, aClosure
, &mUncompressedBuffer
[mNextByte
], *aBytesReadOut
,
131 numToWrite
, &numWritten
);
133 // As defined in nsIInputputStream.idl, do not pass writer func errors.
139 if (numWritten
== 0) {
143 *aBytesReadOut
+= numWritten
;
144 mNextByte
+= numWritten
;
145 MOZ_ASSERT(mNextByte
<= mUncompressedBytes
);
147 if (mNextByte
== mUncompressedBytes
) {
149 mUncompressedBytes
= 0;
152 aCount
-= numWritten
;
157 // Otherwise uncompress the next chunk and loop. Any resulting data
158 // will set mUncompressedBytes which we check at the top of the loop.
160 rv
= ParseNextChunk(&bytesRead
);
161 if (NS_FAILED(rv
)) { return rv
; }
163 // If we couldn't read anything and there is no more data to provide
164 // to the caller, then this is eof.
165 if (bytesRead
== 0 && mUncompressedBytes
== 0) {
174 SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut
)
176 *aNonBlockingOut
= false;
180 SnappyUncompressInputStream::~SnappyUncompressInputStream()
186 SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut
)
188 // There must not be any uncompressed data already in mUncompressedBuffer.
189 MOZ_ASSERT(mUncompressedBytes
== 0);
190 MOZ_ASSERT(mNextByte
== 0);
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 if (!mUncompressedBuffer
) {
199 mUncompressedBuffer
.reset(new (fallible
) char[snappy::kBlockSize
]);
200 if (NS_WARN_IF(!mUncompressedBuffer
)) {
201 return NS_ERROR_OUT_OF_MEMORY
;
205 if (!mCompressedBuffer
) {
206 mCompressedBuffer
.reset(new (fallible
) char[CompressedBufferLength()]);
207 if (NS_WARN_IF(!mCompressedBuffer
)) {
208 return NS_ERROR_OUT_OF_MEMORY
;
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 if (mNeedFirstStreamIdentifier
) {
216 const uint32_t firstReadLength
= kHeaderLength
+
217 kStreamIdentifierDataLength
+
219 MOZ_ASSERT(firstReadLength
<= CompressedBufferLength());
221 rv
= ReadAll(mCompressedBuffer
.get(), firstReadLength
, firstReadLength
,
223 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) { return rv
; }
225 rv
= ParseHeader(mCompressedBuffer
.get(), kHeaderLength
,
226 &mNextChunkType
, &mNextChunkDataLength
);
227 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
228 if (NS_WARN_IF(mNextChunkType
!= StreamIdentifier
||
229 mNextChunkDataLength
!= kStreamIdentifierDataLength
)) {
230 return NS_ERROR_CORRUPTED_CONTENT
;
232 size_t offset
= kHeaderLength
;
234 mNeedFirstStreamIdentifier
= false;
238 rv
= ParseData(mUncompressedBuffer
.get(), snappy::kBlockSize
, mNextChunkType
,
239 &mCompressedBuffer
[offset
],
240 mNextChunkDataLength
, &numWritten
, &numRead
);
241 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
242 MOZ_ASSERT(numWritten
== 0);
243 MOZ_ASSERT(numRead
== mNextChunkDataLength
);
246 rv
= ParseHeader(&mCompressedBuffer
[offset
], *aBytesReadOut
- offset
,
247 &mNextChunkType
, &mNextChunkDataLength
);
248 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
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 if (mNextChunkType
== Unknown
) {
258 rv
= ReadAll(mCompressedBuffer
.get(), kHeaderLength
, kHeaderLength
,
260 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) { return rv
; }
262 rv
= ParseHeader(mCompressedBuffer
.get(), kHeaderLength
,
263 &mNextChunkType
, &mNextChunkDataLength
);
264 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
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 uint32_t readLength
= mNextChunkDataLength
;
272 MOZ_ASSERT(readLength
<= CompressedBufferLength());
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.
277 rv
= mBaseStream
->Available(&avail
);
278 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
279 if (avail
>= (readLength
+ kHeaderLength
)) {
280 readLength
+= kHeaderLength
;
281 MOZ_ASSERT(readLength
<= CompressedBufferLength());
284 rv
= ReadAll(mCompressedBuffer
.get(), readLength
, mNextChunkDataLength
,
286 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) { return rv
; }
290 rv
= ParseData(mUncompressedBuffer
.get(), snappy::kBlockSize
, mNextChunkType
,
291 mCompressedBuffer
.get(), mNextChunkDataLength
,
292 &numWritten
, &numRead
);
293 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
294 MOZ_ASSERT(numRead
== mNextChunkDataLength
);
296 mUncompressedBytes
= numWritten
;
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 if (*aBytesReadOut
<= mNextChunkDataLength
) {
302 mNextChunkType
= Unknown
;
303 mNextChunkDataLength
= 0;
307 // We got the next chunk header. Parse it so that we are ready to for the
308 // next call into this method.
309 rv
= ParseHeader(&mCompressedBuffer
[numRead
], *aBytesReadOut
- numRead
,
310 &mNextChunkType
, &mNextChunkDataLength
);
311 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
317 SnappyUncompressInputStream::ReadAll(char* aBuf
, uint32_t aCount
,
318 uint32_t aMinValidCount
,
319 uint32_t* aBytesReadOut
)
321 MOZ_ASSERT(aCount
>= aMinValidCount
);
326 return NS_BASE_STREAM_CLOSED
;
331 uint32_t bytesRead
= 0;
332 nsresult rv
= mBaseStream
->Read(aBuf
+ offset
, aCount
, &bytesRead
);
333 if (NS_WARN_IF(NS_FAILED(rv
))) { return rv
; }
335 // EOF, but don't immediately return. We need to validate min read bytes
337 if (bytesRead
== 0) {
341 *aBytesReadOut
+= bytesRead
;
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 if (*aBytesReadOut
!= 0 && *aBytesReadOut
< aMinValidCount
) {
349 return NS_ERROR_CORRUPTED_CONTENT
;
356 SnappyUncompressInputStream::UncompressedLength() const
358 MOZ_ASSERT(mNextByte
<= mUncompressedBytes
);
359 return mUncompressedBytes
- mNextByte
;
362 } // namespace mozilla