Bug 1861963 [wpt PR 42839] - Fix accessing update_properties for product, a=testonly
[gecko.git] / xpcom / io / SnappyUncompressInputStream.cpp
blob2872c8c7a2601a4c66cfe45037ce00726271b448
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"
9 #include <algorithm>
10 #include "nsIAsyncInputStream.h"
11 #include "nsStreamUtils.h"
12 #include "snappy/snappy.h"
14 namespace mozilla {
16 NS_IMPL_ISUPPORTS(SnappyUncompressInputStream, nsIInputStream);
18 // Putting kCompressedBufferLength inside a function avoids a static
19 // constructor.
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),
32 mNextByte(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
42 #ifdef DEBUG
43 bool baseNonBlocking;
44 nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
45 MOZ_ASSERT(NS_SUCCEEDED(rv));
46 if (baseNonBlocking) {
47 nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream);
48 MOZ_ASSERT(!async);
50 #endif
53 NS_IMETHODIMP
54 SnappyUncompressInputStream::Close() {
55 if (!mBaseStream) {
56 return NS_OK;
59 mBaseStream->Close();
60 mBaseStream = nullptr;
62 mUncompressedBuffer = nullptr;
63 mCompressedBuffer = nullptr;
65 return NS_OK;
68 NS_IMETHODIMP
69 SnappyUncompressInputStream::Available(uint64_t* aLengthOut) {
70 if (!mBaseStream) {
71 return NS_BASE_STREAM_CLOSED;
74 // If we have uncompressed bytes, then we are done.
75 *aLengthOut = UncompressedLength();
76 if (*aLengthOut > 0) {
77 return NS_OK;
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.
83 uint32_t bytesRead;
84 do {
85 nsresult rv = ParseNextChunk(&bytesRead);
86 if (NS_WARN_IF(NS_FAILED(rv))) {
87 return rv;
89 *aLengthOut = UncompressedLength();
90 } while (*aLengthOut == 0 && bytesRead);
92 return NS_OK;
95 NS_IMETHODIMP
96 SnappyUncompressInputStream::StreamStatus() {
97 if (!mBaseStream) {
98 return NS_BASE_STREAM_CLOSED;
101 // If we have uncompressed bytes, then we're still open.
102 if (UncompressedLength() > 0) {
103 return NS_OK;
106 // Otherwise we'll need to read from the underlying stream, so check it
107 return mBaseStream->StreamStatus();
110 NS_IMETHODIMP
111 SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount,
112 uint32_t* aBytesReadOut) {
113 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut);
116 NS_IMETHODIMP
117 SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter,
118 void* aClosure, uint32_t aCount,
119 uint32_t* aBytesReadOut) {
120 *aBytesReadOut = 0;
122 if (!mBaseStream) {
123 return NS_BASE_STREAM_CLOSED;
126 nsresult rv;
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.
133 while (aCount > 0) {
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);
140 uint32_t numWritten;
141 rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte],
142 *aBytesReadOut, numToWrite, &numWritten);
144 // As defined in nsIInputputStream.idl, do not pass writer func errors.
145 if (NS_FAILED(rv)) {
146 return NS_OK;
149 // End-of-file
150 if (numWritten == 0) {
151 return NS_OK;
154 *aBytesReadOut += numWritten;
155 mNextByte += numWritten;
156 MOZ_ASSERT(mNextByte <= mUncompressedBytes);
158 if (mNextByte == mUncompressedBytes) {
159 mNextByte = 0;
160 mUncompressedBytes = 0;
163 aCount -= numWritten;
165 continue;
168 // Otherwise uncompress the next chunk and loop. Any resulting data
169 // will set mUncompressedBytes which we check at the top of the loop.
170 uint32_t bytesRead;
171 rv = ParseNextChunk(&bytesRead);
172 if (NS_FAILED(rv)) {
173 return rv;
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) {
179 return NS_OK;
183 return NS_OK;
186 NS_IMETHODIMP
187 SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut) {
188 *aNonBlockingOut = false;
189 return NS_OK;
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);
199 nsresult rv;
200 *aBytesReadOut = 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,
228 aBytesReadOut);
229 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) {
230 return rv;
233 rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, &mNextChunkType,
234 &mNextChunkDataLength);
235 if (NS_WARN_IF(NS_FAILED(rv))) {
236 return 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;
246 size_t numRead;
247 size_t numWritten;
248 rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize,
249 mNextChunkType, &mCompressedBuffer[offset],
250 mNextChunkDataLength, &numWritten, &numRead);
251 if (NS_WARN_IF(NS_FAILED(rv))) {
252 return rv;
254 MOZ_ASSERT(numWritten == 0);
255 MOZ_ASSERT(numRead == mNextChunkDataLength);
256 offset += numRead;
258 rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset,
259 &mNextChunkType, &mNextChunkDataLength);
260 if (NS_WARN_IF(NS_FAILED(rv))) {
261 return rv;
264 return NS_OK;
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,
273 aBytesReadOut);
274 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) {
275 return rv;
278 rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, &mNextChunkType,
279 &mNextChunkDataLength);
280 if (NS_WARN_IF(NS_FAILED(rv))) {
281 return rv;
284 return NS_OK;
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.
294 uint64_t avail;
295 rv = mBaseStream->Available(&avail);
296 if (NS_WARN_IF(NS_FAILED(rv))) {
297 return rv;
299 if (avail >= (readLength + kHeaderLength)) {
300 readLength += kHeaderLength;
301 MOZ_ASSERT(readLength <= CompressedBufferLength());
304 rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength,
305 aBytesReadOut);
306 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) {
307 return rv;
310 size_t numRead;
311 size_t numWritten;
312 rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
313 mCompressedBuffer.get(), mNextChunkDataLength, &numWritten,
314 &numRead);
315 if (NS_WARN_IF(NS_FAILED(rv))) {
316 return 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;
328 return NS_OK;
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))) {
336 return rv;
339 return NS_OK;
342 nsresult SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount,
343 uint32_t aMinValidCount,
344 uint32_t* aBytesReadOut) {
345 MOZ_ASSERT(aCount >= aMinValidCount);
347 *aBytesReadOut = 0;
349 if (!mBaseStream) {
350 return NS_BASE_STREAM_CLOSED;
353 uint32_t offset = 0;
354 while (aCount > 0) {
355 uint32_t bytesRead = 0;
356 nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead);
357 if (NS_WARN_IF(NS_FAILED(rv))) {
358 return rv;
361 // EOF, but don't immediately return. We need to validate min read bytes
362 // below.
363 if (bytesRead == 0) {
364 break;
367 *aBytesReadOut += bytesRead;
368 offset += bytesRead;
369 aCount -= 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;
378 return NS_OK;
381 size_t SnappyUncompressInputStream::UncompressedLength() const {
382 MOZ_ASSERT(mNextByte <= mUncompressedBytes);
383 return mUncompressedBytes - mNextByte;
386 } // namespace mozilla