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
, nsIInputStream
);
18 // Putting kCompressedBufferLength inside a function avoids a static
20 static size_t CompressedBufferLength() {
21 static size_t kCompressedBufferLength
=
22 detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize
);
24 MOZ_ASSERT(kCompressedBufferLength
> 0);
25 return kCompressedBufferLength
;
28 SnappyUncompressInputStream::SnappyUncompressInputStream(
29 nsIInputStream
* aBaseStream
)
30 : mBaseStream(aBaseStream
),
31 mUncompressedBytes(0),
33 mNextChunkType(Unknown
),
34 mNextChunkDataLength(0),
35 mNeedFirstStreamIdentifier(true) {
36 // This implementation only supports sync base streams. Verify this in debug
37 // builds. Note, this is a bit complicated because the streams we support
38 // advertise different capabilities:
39 // - nsFileInputStream - blocking and sync
40 // - nsStringInputStream - non-blocking and sync
41 // - nsPipeInputStream - can be blocking, but provides async interface
44 nsresult rv
= mBaseStream
->IsNonBlocking(&baseNonBlocking
);
45 MOZ_ASSERT(NS_SUCCEEDED(rv
));
46 if (baseNonBlocking
) {
47 nsCOMPtr
<nsIAsyncInputStream
> async
= do_QueryInterface(mBaseStream
);
54 SnappyUncompressInputStream::Close() {
60 mBaseStream
= nullptr;
62 mUncompressedBuffer
= nullptr;
63 mCompressedBuffer
= nullptr;
69 SnappyUncompressInputStream::Available(uint64_t* aLengthOut
) {
71 return NS_BASE_STREAM_CLOSED
;
74 // If we have uncompressed bytes, then we are done.
75 *aLengthOut
= UncompressedLength();
76 if (*aLengthOut
> 0) {
80 // Otherwise, attempt to uncompress bytes until we get something or the
81 // underlying stream is drained. We loop here because some chunks can
82 // be StreamIdentifiers, padding, etc with no data.
85 nsresult rv
= ParseNextChunk(&bytesRead
);
86 if (NS_WARN_IF(NS_FAILED(rv
))) {
89 *aLengthOut
= UncompressedLength();
90 } while (*aLengthOut
== 0 && bytesRead
);
96 SnappyUncompressInputStream::StreamStatus() {
98 return NS_BASE_STREAM_CLOSED
;
101 // If we have uncompressed bytes, then we're still open.
102 if (UncompressedLength() > 0) {
106 // Otherwise we'll need to read from the underlying stream, so check it
107 return mBaseStream
->StreamStatus();
111 SnappyUncompressInputStream::Read(char* aBuf
, uint32_t aCount
,
112 uint32_t* aBytesReadOut
) {
113 return ReadSegments(NS_CopySegmentToBuffer
, aBuf
, aCount
, aBytesReadOut
);
117 SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter
,
118 void* aClosure
, uint32_t aCount
,
119 uint32_t* aBytesReadOut
) {
123 return NS_BASE_STREAM_CLOSED
;
128 // Do not try to use the base stream's ReadSegements here. Its very
129 // unlikely we will get a single buffer that contains all of the compressed
130 // data and therefore would have to copy into our own buffer anyways.
131 // Instead, focus on making efficient use of the Read() interface.
134 // We have some decompressed data in our buffer. Provide it to the
135 // callers writer function.
136 if (mUncompressedBytes
> 0) {
137 MOZ_ASSERT(mUncompressedBuffer
);
138 uint32_t remaining
= UncompressedLength();
139 uint32_t numToWrite
= std::min(aCount
, remaining
);
141 rv
= aWriter(this, aClosure
, &mUncompressedBuffer
[mNextByte
],
142 *aBytesReadOut
, numToWrite
, &numWritten
);
144 // As defined in nsIInputputStream.idl, do not pass writer func errors.
150 if (numWritten
== 0) {
154 *aBytesReadOut
+= numWritten
;
155 mNextByte
+= numWritten
;
156 MOZ_ASSERT(mNextByte
<= mUncompressedBytes
);
158 if (mNextByte
== mUncompressedBytes
) {
160 mUncompressedBytes
= 0;
163 aCount
-= numWritten
;
168 // Otherwise uncompress the next chunk and loop. Any resulting data
169 // will set mUncompressedBytes which we check at the top of the loop.
171 rv
= ParseNextChunk(&bytesRead
);
176 // If we couldn't read anything and there is no more data to provide
177 // to the caller, then this is eof.
178 if (bytesRead
== 0 && mUncompressedBytes
== 0) {
187 SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut
) {
188 *aNonBlockingOut
= false;
192 SnappyUncompressInputStream::~SnappyUncompressInputStream() { Close(); }
194 nsresult
SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut
) {
195 // There must not be any uncompressed data already in mUncompressedBuffer.
196 MOZ_ASSERT(mUncompressedBytes
== 0);
197 MOZ_ASSERT(mNextByte
== 0);
202 // Lazily create our two buffers so we can report OOM during stream
203 // operation. These allocations only happens once. The buffers are reused
204 // until the stream is closed.
205 if (!mUncompressedBuffer
) {
206 mUncompressedBuffer
.reset(new (fallible
) char[snappy::kBlockSize
]);
207 if (NS_WARN_IF(!mUncompressedBuffer
)) {
208 return NS_ERROR_OUT_OF_MEMORY
;
212 if (!mCompressedBuffer
) {
213 mCompressedBuffer
.reset(new (fallible
) char[CompressedBufferLength()]);
214 if (NS_WARN_IF(!mCompressedBuffer
)) {
215 return NS_ERROR_OUT_OF_MEMORY
;
219 // We have no decompressed data and we also have not seen the start of stream
220 // yet. Read and validate the StreamIdentifier chunk. Also read the next
221 // header to determine the size of the first real data chunk.
222 if (mNeedFirstStreamIdentifier
) {
223 const uint32_t firstReadLength
=
224 kHeaderLength
+ kStreamIdentifierDataLength
+ kHeaderLength
;
225 MOZ_ASSERT(firstReadLength
<= CompressedBufferLength());
227 rv
= ReadAll(mCompressedBuffer
.get(), firstReadLength
, firstReadLength
,
229 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) {
233 rv
= ParseHeader(mCompressedBuffer
.get(), kHeaderLength
, &mNextChunkType
,
234 &mNextChunkDataLength
);
235 if (NS_WARN_IF(NS_FAILED(rv
))) {
238 if (NS_WARN_IF(mNextChunkType
!= StreamIdentifier
||
239 mNextChunkDataLength
!= kStreamIdentifierDataLength
)) {
240 return NS_ERROR_CORRUPTED_CONTENT
;
242 size_t offset
= kHeaderLength
;
244 mNeedFirstStreamIdentifier
= false;
248 rv
= ParseData(mUncompressedBuffer
.get(), snappy::kBlockSize
,
249 mNextChunkType
, &mCompressedBuffer
[offset
],
250 mNextChunkDataLength
, &numWritten
, &numRead
);
251 if (NS_WARN_IF(NS_FAILED(rv
))) {
254 MOZ_ASSERT(numWritten
== 0);
255 MOZ_ASSERT(numRead
== mNextChunkDataLength
);
258 rv
= ParseHeader(&mCompressedBuffer
[offset
], *aBytesReadOut
- offset
,
259 &mNextChunkType
, &mNextChunkDataLength
);
260 if (NS_WARN_IF(NS_FAILED(rv
))) {
267 // We have no compressed data and we don't know how big the next chunk is.
268 // This happens when we get an EOF pause in the middle of a stream and also
269 // at the end of the stream. Simply read the next header and return. The
270 // chunk body will be read on the next entry into this method.
271 if (mNextChunkType
== Unknown
) {
272 rv
= ReadAll(mCompressedBuffer
.get(), kHeaderLength
, kHeaderLength
,
274 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) {
278 rv
= ParseHeader(mCompressedBuffer
.get(), kHeaderLength
, &mNextChunkType
,
279 &mNextChunkDataLength
);
280 if (NS_WARN_IF(NS_FAILED(rv
))) {
287 // We have no decompressed data, but we do know the size of the next chunk.
288 // Read at least that much from the base stream.
289 uint32_t readLength
= mNextChunkDataLength
;
290 MOZ_ASSERT(readLength
<= CompressedBufferLength());
292 // However, if there is enough data in the base stream, also read the next
293 // chunk header. This helps optimize the stream by avoiding many small reads.
295 rv
= mBaseStream
->Available(&avail
);
296 if (NS_WARN_IF(NS_FAILED(rv
))) {
299 if (avail
>= (readLength
+ kHeaderLength
)) {
300 readLength
+= kHeaderLength
;
301 MOZ_ASSERT(readLength
<= CompressedBufferLength());
304 rv
= ReadAll(mCompressedBuffer
.get(), readLength
, mNextChunkDataLength
,
306 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) {
312 rv
= ParseData(mUncompressedBuffer
.get(), snappy::kBlockSize
, mNextChunkType
,
313 mCompressedBuffer
.get(), mNextChunkDataLength
, &numWritten
,
315 if (NS_WARN_IF(NS_FAILED(rv
))) {
318 MOZ_ASSERT(numRead
== mNextChunkDataLength
);
320 mUncompressedBytes
= numWritten
;
322 // If we were unable to directly read the next chunk header, then clear
323 // our internal state. We will have to perform a small read to get the
324 // header the next time we enter this method.
325 if (*aBytesReadOut
<= mNextChunkDataLength
) {
326 mNextChunkType
= Unknown
;
327 mNextChunkDataLength
= 0;
331 // We got the next chunk header. Parse it so that we are ready to for the
332 // next call into this method.
333 rv
= ParseHeader(&mCompressedBuffer
[numRead
], *aBytesReadOut
- numRead
,
334 &mNextChunkType
, &mNextChunkDataLength
);
335 if (NS_WARN_IF(NS_FAILED(rv
))) {
342 nsresult
SnappyUncompressInputStream::ReadAll(char* aBuf
, uint32_t aCount
,
343 uint32_t aMinValidCount
,
344 uint32_t* aBytesReadOut
) {
345 MOZ_ASSERT(aCount
>= aMinValidCount
);
350 return NS_BASE_STREAM_CLOSED
;
355 uint32_t bytesRead
= 0;
356 nsresult rv
= mBaseStream
->Read(aBuf
+ offset
, aCount
, &bytesRead
);
357 if (NS_WARN_IF(NS_FAILED(rv
))) {
361 // EOF, but don't immediately return. We need to validate min read bytes
363 if (bytesRead
== 0) {
367 *aBytesReadOut
+= bytesRead
;
372 // Reading zero bytes is not an error. Its the expected EOF condition.
373 // Only compare to the minimum valid count if we read at least one byte.
374 if (*aBytesReadOut
!= 0 && *aBytesReadOut
< aMinValidCount
) {
375 return NS_ERROR_CORRUPTED_CONTENT
;
381 size_t SnappyUncompressInputStream::UncompressedLength() const {
382 MOZ_ASSERT(mNextByte
<= mUncompressedBytes
);
383 return mUncompressedBytes
- mNextByte
;
386 } // namespace mozilla