Coverage Report

Created: 2018-09-25 14:53

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