LCOV - code coverage report
Current view: top level - xpcom/io - SnappyUncompressInputStream.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 158 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 15 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13