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/SnappyCompressOutputStream.h"
8 :
9 : #include <algorithm>
10 : #include "nsStreamUtils.h"
11 : #include "snappy/snappy.h"
12 :
13 : namespace mozilla {
14 :
15 0 : NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream);
16 :
17 : // static
18 : const size_t
19 : SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize;
20 :
21 0 : SnappyCompressOutputStream::SnappyCompressOutputStream(nsIOutputStream* aBaseStream,
22 0 : size_t aBlockSize)
23 : : mBaseStream(aBaseStream)
24 0 : , mBlockSize(std::min(aBlockSize, kMaxBlockSize))
25 : , mNextByte(0)
26 : , mCompressedBufferLength(0)
27 0 : , mStreamIdentifierWritten(false)
28 : {
29 0 : MOZ_ASSERT(mBlockSize > 0);
30 :
31 : // This implementation only supports sync base streams. Verify this in debug
32 : // builds. Note, this can be simpler than the check in
33 : // SnappyUncompressInputStream because we don't have to deal with the
34 : // nsStringInputStream oddness of being non-blocking and sync.
35 : #ifdef DEBUG
36 : bool baseNonBlocking;
37 0 : nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
38 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
39 0 : MOZ_ASSERT(!baseNonBlocking);
40 : #endif
41 0 : }
42 :
43 : size_t
44 0 : SnappyCompressOutputStream::BlockSize() const
45 : {
46 0 : return mBlockSize;
47 : }
48 :
49 : NS_IMETHODIMP
50 0 : SnappyCompressOutputStream::Close()
51 : {
52 0 : if (!mBaseStream) {
53 0 : return NS_OK;
54 : }
55 :
56 0 : nsresult rv = Flush();
57 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
58 :
59 0 : mBaseStream->Close();
60 0 : mBaseStream = nullptr;
61 :
62 0 : mBuffer = nullptr;
63 0 : mCompressedBuffer = nullptr;
64 :
65 0 : return NS_OK;
66 : }
67 :
68 : NS_IMETHODIMP
69 0 : SnappyCompressOutputStream::Flush()
70 : {
71 0 : if (!mBaseStream) {
72 0 : return NS_BASE_STREAM_CLOSED;
73 : }
74 :
75 0 : nsresult rv = FlushToBaseStream();
76 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
77 :
78 0 : mBaseStream->Flush();
79 :
80 0 : return NS_OK;
81 : }
82 :
83 : NS_IMETHODIMP
84 0 : SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount,
85 : uint32_t* aResultOut)
86 : {
87 : return WriteSegments(NS_CopySegmentToBuffer, const_cast<char*>(aBuf), aCount,
88 0 : aResultOut);
89 : }
90 :
91 : NS_IMETHODIMP
92 0 : SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*)
93 : {
94 0 : return NS_ERROR_NOT_IMPLEMENTED;
95 : }
96 :
97 : NS_IMETHODIMP
98 0 : SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader,
99 : void* aClosure,
100 : uint32_t aCount,
101 : uint32_t* aBytesWrittenOut)
102 : {
103 0 : *aBytesWrittenOut = 0;
104 :
105 0 : if (!mBaseStream) {
106 0 : return NS_BASE_STREAM_CLOSED;
107 : }
108 :
109 0 : if (!mBuffer) {
110 0 : mBuffer.reset(new (fallible) char[mBlockSize]);
111 0 : if (NS_WARN_IF(!mBuffer)) {
112 0 : return NS_ERROR_OUT_OF_MEMORY;
113 : }
114 : }
115 :
116 0 : while (aCount > 0) {
117 : // Determine how much space is left in our flat, uncompressed buffer.
118 0 : MOZ_ASSERT(mNextByte <= mBlockSize);
119 0 : uint32_t remaining = mBlockSize - mNextByte;
120 :
121 : // If it is full, then compress and flush the data to the base stream.
122 0 : if (remaining == 0) {
123 0 : nsresult rv = FlushToBaseStream();
124 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
125 :
126 : // Now the entire buffer should be available for copying.
127 0 : MOZ_ASSERT(!mNextByte);
128 0 : remaining = mBlockSize;
129 : }
130 :
131 0 : uint32_t numToRead = std::min(remaining, aCount);
132 0 : uint32_t numRead = 0;
133 :
134 0 : nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte],
135 0 : *aBytesWrittenOut, numToRead, &numRead);
136 :
137 : // As defined in nsIOutputStream.idl, do not pass reader func errors.
138 0 : if (NS_FAILED(rv)) {
139 0 : return NS_OK;
140 : }
141 :
142 : // End-of-file
143 0 : if (numRead == 0) {
144 0 : return NS_OK;
145 : }
146 :
147 0 : mNextByte += numRead;
148 0 : *aBytesWrittenOut += numRead;
149 0 : aCount -= numRead;
150 : }
151 :
152 0 : return NS_OK;
153 : }
154 :
155 : NS_IMETHODIMP
156 0 : SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut)
157 : {
158 0 : *aNonBlockingOut = false;
159 0 : return NS_OK;
160 : }
161 :
162 0 : SnappyCompressOutputStream::~SnappyCompressOutputStream()
163 : {
164 0 : Close();
165 0 : }
166 :
167 : nsresult
168 0 : SnappyCompressOutputStream::FlushToBaseStream()
169 : {
170 0 : MOZ_ASSERT(mBaseStream);
171 :
172 : // Lazily create the compressed buffer on our first flush. This
173 : // allows us to report OOM during stream operation. This buffer
174 : // will then get re-used until the stream is closed.
175 0 : if (!mCompressedBuffer) {
176 0 : mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize);
177 0 : mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]);
178 0 : if (NS_WARN_IF(!mCompressedBuffer)) {
179 0 : return NS_ERROR_OUT_OF_MEMORY;
180 : }
181 : }
182 :
183 : // The first chunk must be a StreamIdentifier chunk. Write it out
184 : // if we have not done so already.
185 0 : nsresult rv = MaybeFlushStreamIdentifier();
186 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
187 :
188 : // Compress the data to our internal compressed buffer.
189 : size_t compressedLength;
190 0 : rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength,
191 0 : mBuffer.get(), mNextByte, &compressedLength);
192 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
193 0 : MOZ_ASSERT(compressedLength > 0);
194 :
195 0 : mNextByte = 0;
196 :
197 : // Write the compressed buffer out to the base stream.
198 0 : uint32_t numWritten = 0;
199 0 : rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
200 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
201 0 : MOZ_ASSERT(compressedLength == numWritten);
202 :
203 0 : return NS_OK;
204 : }
205 :
206 : nsresult
207 0 : SnappyCompressOutputStream::MaybeFlushStreamIdentifier()
208 : {
209 0 : MOZ_ASSERT(mCompressedBuffer);
210 :
211 0 : if (mStreamIdentifierWritten) {
212 0 : return NS_OK;
213 : }
214 :
215 : // Build the StreamIdentifier in our compressed buffer.
216 : size_t compressedLength;
217 0 : nsresult rv = WriteStreamIdentifier(mCompressedBuffer.get(),
218 : mCompressedBufferLength,
219 0 : &compressedLength);
220 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
221 :
222 : // Write the compressed buffer out to the base stream.
223 0 : uint32_t numWritten = 0;
224 0 : rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
225 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
226 0 : MOZ_ASSERT(compressedLength == numWritten);
227 :
228 0 : mStreamIdentifierWritten = true;
229 :
230 0 : return NS_OK;
231 : }
232 :
233 : nsresult
234 0 : SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount,
235 : uint32_t* aBytesWrittenOut)
236 : {
237 0 : *aBytesWrittenOut = 0;
238 :
239 0 : if (!mBaseStream) {
240 0 : return NS_BASE_STREAM_CLOSED;
241 : }
242 :
243 0 : uint32_t offset = 0;
244 0 : while (aCount > 0) {
245 0 : uint32_t numWritten = 0;
246 0 : nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten);
247 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
248 0 : offset += numWritten;
249 0 : aCount -= numWritten;
250 0 : *aBytesWrittenOut += numWritten;
251 : }
252 :
253 0 : return NS_OK;
254 : }
255 :
256 : } // namespace mozilla
|