Bug 1466812 [wpt PR 11352] - [css-contain] Fix references to paint instead of layout...
[gecko.git] / xpcom / io / SnappyUncompressInputStream.cpp
blobe0a1b6f4cd5a19caed70cc91b770f928362bcfe4
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,
17 nsIInputStream);
19 // Putting kCompressedBufferLength inside a function avoids a static
20 // constructor.
21 static size_t CompressedBufferLength()
23 static size_t kCompressedBufferLength =
24 detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize);
26 MOZ_ASSERT(kCompressedBufferLength > 0);
27 return kCompressedBufferLength;
30 SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream)
31 : mBaseStream(aBaseStream)
32 , mUncompressedBytes(0)
33 , mNextByte(0)
34 , mNextChunkType(Unknown)
35 , mNextChunkDataLength(0)
36 , mNeedFirstStreamIdentifier(true)
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 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);
52 #endif
55 NS_IMETHODIMP
56 SnappyUncompressInputStream::Close()
58 if (!mBaseStream) {
59 return NS_OK;
62 mBaseStream->Close();
63 mBaseStream = nullptr;
65 mUncompressedBuffer = nullptr;
66 mCompressedBuffer = nullptr;
68 return NS_OK;
71 NS_IMETHODIMP
72 SnappyUncompressInputStream::Available(uint64_t* aLengthOut)
74 if (!mBaseStream) {
75 return NS_BASE_STREAM_CLOSED;
78 // If we have uncompressed bytes, then we are done.
79 *aLengthOut = UncompressedLength();
80 if (*aLengthOut > 0) {
81 return NS_OK;
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 do {
89 nsresult rv = ParseNextChunk(&bytesRead);
90 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
91 *aLengthOut = UncompressedLength();
92 } while(*aLengthOut == 0 && bytesRead);
94 return NS_OK;
97 NS_IMETHODIMP
98 SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount,
99 uint32_t* aBytesReadOut)
101 return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut);
104 NS_IMETHODIMP
105 SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter,
106 void* aClosure, uint32_t aCount,
107 uint32_t* aBytesReadOut)
109 *aBytesReadOut = 0;
111 if (!mBaseStream) {
112 return NS_BASE_STREAM_CLOSED;
115 nsresult rv;
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.
122 while (aCount > 0) {
123 // We have some decompressed data in our buffer. Provide it to the
124 // callers writer function.
125 if (mUncompressedBytes > 0) {
126 MOZ_ASSERT(mUncompressedBuffer);
127 uint32_t remaining = UncompressedLength();
128 uint32_t numToWrite = std::min(aCount, remaining);
129 uint32_t numWritten;
130 rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut,
131 numToWrite, &numWritten);
133 // As defined in nsIInputputStream.idl, do not pass writer func errors.
134 if (NS_FAILED(rv)) {
135 return NS_OK;
138 // End-of-file
139 if (numWritten == 0) {
140 return NS_OK;
143 *aBytesReadOut += numWritten;
144 mNextByte += numWritten;
145 MOZ_ASSERT(mNextByte <= mUncompressedBytes);
147 if (mNextByte == mUncompressedBytes) {
148 mNextByte = 0;
149 mUncompressedBytes = 0;
152 aCount -= numWritten;
154 continue;
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 rv = ParseNextChunk(&bytesRead);
161 if (NS_FAILED(rv)) { return rv; }
163 // If we couldn't read anything and there is no more data to provide
164 // to the caller, then this is eof.
165 if (bytesRead == 0 && mUncompressedBytes == 0) {
166 return NS_OK;
170 return NS_OK;
173 NS_IMETHODIMP
174 SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut)
176 *aNonBlockingOut = false;
177 return NS_OK;
180 SnappyUncompressInputStream::~SnappyUncompressInputStream()
182 Close();
185 nsresult
186 SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut)
188 // There must not be any uncompressed data already in mUncompressedBuffer.
189 MOZ_ASSERT(mUncompressedBytes == 0);
190 MOZ_ASSERT(mNextByte == 0);
192 nsresult rv;
193 *aBytesReadOut = 0;
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 if (!mUncompressedBuffer) {
199 mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]);
200 if (NS_WARN_IF(!mUncompressedBuffer)) {
201 return NS_ERROR_OUT_OF_MEMORY;
205 if (!mCompressedBuffer) {
206 mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]);
207 if (NS_WARN_IF(!mCompressedBuffer)) {
208 return NS_ERROR_OUT_OF_MEMORY;
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 if (mNeedFirstStreamIdentifier) {
216 const uint32_t firstReadLength = kHeaderLength +
217 kStreamIdentifierDataLength +
218 kHeaderLength;
219 MOZ_ASSERT(firstReadLength <= CompressedBufferLength());
221 rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength,
222 aBytesReadOut);
223 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
225 rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
226 &mNextChunkType, &mNextChunkDataLength);
227 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
228 if (NS_WARN_IF(mNextChunkType != StreamIdentifier ||
229 mNextChunkDataLength != kStreamIdentifierDataLength)) {
230 return NS_ERROR_CORRUPTED_CONTENT;
232 size_t offset = kHeaderLength;
234 mNeedFirstStreamIdentifier = false;
236 size_t numRead;
237 size_t numWritten;
238 rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
239 &mCompressedBuffer[offset],
240 mNextChunkDataLength, &numWritten, &numRead);
241 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
242 MOZ_ASSERT(numWritten == 0);
243 MOZ_ASSERT(numRead == mNextChunkDataLength);
244 offset += numRead;
246 rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset,
247 &mNextChunkType, &mNextChunkDataLength);
248 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
250 return NS_OK;
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 if (mNextChunkType == Unknown) {
258 rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength,
259 aBytesReadOut);
260 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
262 rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
263 &mNextChunkType, &mNextChunkDataLength);
264 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
266 return NS_OK;
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 uint32_t readLength = mNextChunkDataLength;
272 MOZ_ASSERT(readLength <= CompressedBufferLength());
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 rv = mBaseStream->Available(&avail);
278 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
279 if (avail >= (readLength + kHeaderLength)) {
280 readLength += kHeaderLength;
281 MOZ_ASSERT(readLength <= CompressedBufferLength());
284 rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength,
285 aBytesReadOut);
286 if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
288 size_t numRead;
289 size_t numWritten;
290 rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
291 mCompressedBuffer.get(), mNextChunkDataLength,
292 &numWritten, &numRead);
293 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
294 MOZ_ASSERT(numRead == mNextChunkDataLength);
296 mUncompressedBytes = numWritten;
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 if (*aBytesReadOut <= mNextChunkDataLength) {
302 mNextChunkType = Unknown;
303 mNextChunkDataLength = 0;
304 return NS_OK;
307 // We got the next chunk header. Parse it so that we are ready to for the
308 // next call into this method.
309 rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead,
310 &mNextChunkType, &mNextChunkDataLength);
311 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
313 return NS_OK;
316 nsresult
317 SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount,
318 uint32_t aMinValidCount,
319 uint32_t* aBytesReadOut)
321 MOZ_ASSERT(aCount >= aMinValidCount);
323 *aBytesReadOut = 0;
325 if (!mBaseStream) {
326 return NS_BASE_STREAM_CLOSED;
329 uint32_t offset = 0;
330 while (aCount > 0) {
331 uint32_t bytesRead = 0;
332 nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead);
333 if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
335 // EOF, but don't immediately return. We need to validate min read bytes
336 // below.
337 if (bytesRead == 0) {
338 break;
341 *aBytesReadOut += bytesRead;
342 offset += bytesRead;
343 aCount -= bytesRead;
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 if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) {
349 return NS_ERROR_CORRUPTED_CONTENT;
352 return NS_OK;
355 size_t
356 SnappyUncompressInputStream::UncompressedLength() const
358 MOZ_ASSERT(mNextByte <= mUncompressedBytes);
359 return mUncompressedBytes - mNextByte;
362 } // namespace mozilla