gecko-dev/xpcom/io/SnappyUncompressInputStream.cpp
Wes Kocher 6c1a0ae549 Backed out 6 changesets (bug 1371699) for various failures including frequent img-blobURI-2.html failures and leaks a=backout
Backed out changeset 67a27cf0ab80 (bug 1371699)
Backed out changeset 7e494fa90087 (bug 1371699)
Backed out changeset 30405ec37e1e (bug 1371699)
Backed out changeset 2d67624a01dc (bug 1371699)
Backed out changeset 503c9d22e6bb (bug 1371699)
Backed out changeset eedcb67a9fb6 (bug 1371699)

MozReview-Commit-ID: 4HydLjK7Ond
2017-06-13 17:50:11 -07:00

363 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/SnappyUncompressInputStream.h"
#include <algorithm>
#include "nsIAsyncInputStream.h"
#include "nsStreamUtils.h"
#include "snappy/snappy.h"
namespace mozilla {
NS_IMPL_ISUPPORTS(SnappyUncompressInputStream,
nsIInputStream);
// Putting kCompressedBufferLength inside a function avoids a static
// constructor.
static size_t CompressedBufferLength()
{
static size_t kCompressedBufferLength =
detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize);
MOZ_ASSERT(kCompressedBufferLength > 0);
return kCompressedBufferLength;
}
SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream)
: mBaseStream(aBaseStream)
, mUncompressedBytes(0)
, mNextByte(0)
, mNextChunkType(Unknown)
, mNextChunkDataLength(0)
, mNeedFirstStreamIdentifier(true)
{
// This implementation only supports sync base streams. Verify this in debug
// builds. Note, this is a bit complicated because the streams we support
// advertise different capabilities:
// - nsFileInputStream - blocking and sync
// - nsStringInputStream - non-blocking and sync
// - nsPipeInputStream - can be blocking, but provides async interface
#ifdef DEBUG
bool baseNonBlocking;
nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (baseNonBlocking) {
nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream);
MOZ_ASSERT(!async);
}
#endif
}
NS_IMETHODIMP
SnappyUncompressInputStream::Close()
{
if (!mBaseStream) {
return NS_OK;
}
mBaseStream->Close();
mBaseStream = nullptr;
mUncompressedBuffer = nullptr;
mCompressedBuffer = nullptr;
return NS_OK;
}
NS_IMETHODIMP
SnappyUncompressInputStream::Available(uint64_t* aLengthOut)
{
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
// If we have uncompressed bytes, then we are done.
*aLengthOut = UncompressedLength();
if (*aLengthOut > 0) {
return NS_OK;
}
// Otherwise, attempt to uncompress bytes until we get something or the
// underlying stream is drained. We loop here because some chunks can
// be StreamIdentifiers, padding, etc with no data.
uint32_t bytesRead;
do {
nsresult rv = ParseNextChunk(&bytesRead);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
*aLengthOut = UncompressedLength();
} while(*aLengthOut == 0 && bytesRead);
return NS_OK;
}
NS_IMETHODIMP
SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount,
uint32_t* aBytesReadOut)
{
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut);
}
NS_IMETHODIMP
SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter,
void* aClosure, uint32_t aCount,
uint32_t* aBytesReadOut)
{
*aBytesReadOut = 0;
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
nsresult rv;
// Do not try to use the base stream's ReadSegements here. Its very
// unlikely we will get a single buffer that contains all of the compressed
// data and therefore would have to copy into our own buffer anyways.
// Instead, focus on making efficient use of the Read() interface.
while (aCount > 0) {
// We have some decompressed data in our buffer. Provide it to the
// callers writer function.
if (mUncompressedBytes > 0) {
MOZ_ASSERT(mUncompressedBuffer);
uint32_t remaining = UncompressedLength();
uint32_t numToWrite = std::min(aCount, remaining);
uint32_t numWritten;
rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut,
numToWrite, &numWritten);
// As defined in nsIInputputStream.idl, do not pass writer func errors.
if (NS_FAILED(rv)) {
return NS_OK;
}
// End-of-file
if (numWritten == 0) {
return NS_OK;
}
*aBytesReadOut += numWritten;
mNextByte += numWritten;
MOZ_ASSERT(mNextByte <= mUncompressedBytes);
if (mNextByte == mUncompressedBytes) {
mNextByte = 0;
mUncompressedBytes = 0;
}
aCount -= numWritten;
continue;
}
// Otherwise uncompress the next chunk and loop. Any resulting data
// will set mUncompressedBytes which we check at the top of the loop.
uint32_t bytesRead;
rv = ParseNextChunk(&bytesRead);
if (NS_FAILED(rv)) { return rv; }
// If we couldn't read anything and there is no more data to provide
// to the caller, then this is eof.
if (bytesRead == 0 && mUncompressedBytes == 0) {
return NS_OK;
}
}
return NS_OK;
}
NS_IMETHODIMP
SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut)
{
*aNonBlockingOut = false;
return NS_OK;
}
SnappyUncompressInputStream::~SnappyUncompressInputStream()
{
Close();
}
nsresult
SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut)
{
// There must not be any uncompressed data already in mUncompressedBuffer.
MOZ_ASSERT(mUncompressedBytes == 0);
MOZ_ASSERT(mNextByte == 0);
nsresult rv;
*aBytesReadOut = 0;
// Lazily create our two buffers so we can report OOM during stream
// operation. These allocations only happens once. The buffers are reused
// until the stream is closed.
if (!mUncompressedBuffer) {
mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]);
if (NS_WARN_IF(!mUncompressedBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
if (!mCompressedBuffer) {
mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]);
if (NS_WARN_IF(!mCompressedBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
// We have no decompressed data and we also have not seen the start of stream
// yet. Read and validate the StreamIdentifier chunk. Also read the next
// header to determine the size of the first real data chunk.
if (mNeedFirstStreamIdentifier) {
const uint32_t firstReadLength = kHeaderLength +
kStreamIdentifierDataLength +
kHeaderLength;
MOZ_ASSERT(firstReadLength <= CompressedBufferLength());
rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength,
aBytesReadOut);
if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
&mNextChunkType, &mNextChunkDataLength);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(mNextChunkType != StreamIdentifier ||
mNextChunkDataLength != kStreamIdentifierDataLength)) {
return NS_ERROR_CORRUPTED_CONTENT;
}
size_t offset = kHeaderLength;
mNeedFirstStreamIdentifier = false;
size_t numRead;
size_t numWritten;
rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
&mCompressedBuffer[offset],
mNextChunkDataLength, &numWritten, &numRead);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_ASSERT(numWritten == 0);
MOZ_ASSERT(numRead == mNextChunkDataLength);
offset += numRead;
rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset,
&mNextChunkType, &mNextChunkDataLength);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return NS_OK;
}
// We have no compressed data and we don't know how big the next chunk is.
// This happens when we get an EOF pause in the middle of a stream and also
// at the end of the stream. Simply read the next header and return. The
// chunk body will be read on the next entry into this method.
if (mNextChunkType == Unknown) {
rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength,
aBytesReadOut);
if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
&mNextChunkType, &mNextChunkDataLength);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return NS_OK;
}
// We have no decompressed data, but we do know the size of the next chunk.
// Read at least that much from the base stream.
uint32_t readLength = mNextChunkDataLength;
MOZ_ASSERT(readLength <= CompressedBufferLength());
// However, if there is enough data in the base stream, also read the next
// chunk header. This helps optimize the stream by avoiding many small reads.
uint64_t avail;
rv = mBaseStream->Available(&avail);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (avail >= (readLength + kHeaderLength)) {
readLength += kHeaderLength;
MOZ_ASSERT(readLength <= CompressedBufferLength());
}
rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength,
aBytesReadOut);
if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
size_t numRead;
size_t numWritten;
rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
mCompressedBuffer.get(), mNextChunkDataLength,
&numWritten, &numRead);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_ASSERT(numRead == mNextChunkDataLength);
mUncompressedBytes = numWritten;
// If we were unable to directly read the next chunk header, then clear
// our internal state. We will have to perform a small read to get the
// header the next time we enter this method.
if (*aBytesReadOut <= mNextChunkDataLength) {
mNextChunkType = Unknown;
mNextChunkDataLength = 0;
return NS_OK;
}
// We got the next chunk header. Parse it so that we are ready to for the
// next call into this method.
rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead,
&mNextChunkType, &mNextChunkDataLength);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return NS_OK;
}
nsresult
SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount,
uint32_t aMinValidCount,
uint32_t* aBytesReadOut)
{
MOZ_ASSERT(aCount >= aMinValidCount);
*aBytesReadOut = 0;
if (!mBaseStream) {
return NS_BASE_STREAM_CLOSED;
}
uint32_t offset = 0;
while (aCount > 0) {
uint32_t bytesRead = 0;
nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// EOF, but don't immediately return. We need to validate min read bytes
// below.
if (bytesRead == 0) {
break;
}
*aBytesReadOut += bytesRead;
offset += bytesRead;
aCount -= bytesRead;
}
// Reading zero bytes is not an error. Its the expected EOF condition.
// Only compare to the minimum valid count if we read at least one byte.
if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) {
return NS_ERROR_CORRUPTED_CONTENT;
}
return NS_OK;
}
size_t
SnappyUncompressInputStream::UncompressedLength() const
{
MOZ_ASSERT(mNextByte <= mUncompressedBytes);
return mUncompressedBytes - mNextByte;
}
} // namespace mozilla