1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: brianolson@google.com (Brian Olson)
32//
33// This file contains the implementation of classes GzipInputStream and
34// GzipOutputStream.
35
36
37#if HAVE_ZLIB
38#include <google/protobuf/io/gzip_stream.h>
39#include <google/protobuf/port.h>
40
41#include <google/protobuf/stubs/common.h>
42#include <google/protobuf/stubs/logging.h>
43
44namespace google {
45namespace protobuf {
46namespace io {
47
48static const int kDefaultBufferSize = 65536;
49
50GzipInputStream::GzipInputStream(ZeroCopyInputStream* sub_stream, Format format,
51 int buffer_size)
52 : format_(format), sub_stream_(sub_stream), zerror_(Z_OK), byte_count_(0) {
53 zcontext_.state = Z_NULL;
54 zcontext_.zalloc = Z_NULL;
55 zcontext_.zfree = Z_NULL;
56 zcontext_.opaque = Z_NULL;
57 zcontext_.total_out = 0;
58 zcontext_.next_in = NULL;
59 zcontext_.avail_in = 0;
60 zcontext_.total_in = 0;
61 zcontext_.msg = NULL;
62 if (buffer_size == -1) {
63 output_buffer_length_ = kDefaultBufferSize;
64 } else {
65 output_buffer_length_ = buffer_size;
66 }
67 output_buffer_ = operator new(output_buffer_length_);
68 GOOGLE_CHECK(output_buffer_ != NULL);
69 zcontext_.next_out = static_cast<Bytef*>(output_buffer_);
70 zcontext_.avail_out = output_buffer_length_;
71 output_position_ = output_buffer_;
72}
73GzipInputStream::~GzipInputStream() {
74 internal::SizedDelete(p: output_buffer_, size: output_buffer_length_);
75 zerror_ = inflateEnd(strm: &zcontext_);
76}
77
78static inline int internalInflateInit2(z_stream* zcontext,
79 GzipInputStream::Format format) {
80 int windowBitsFormat = 0;
81 switch (format) {
82 case GzipInputStream::GZIP:
83 windowBitsFormat = 16;
84 break;
85 case GzipInputStream::AUTO:
86 windowBitsFormat = 32;
87 break;
88 case GzipInputStream::ZLIB:
89 windowBitsFormat = 0;
90 break;
91 }
92 return inflateInit2(zcontext, /* windowBits */ 15 | windowBitsFormat);
93}
94
95int GzipInputStream::Inflate(int flush) {
96 if ((zerror_ == Z_OK) && (zcontext_.avail_out == 0)) {
97 // previous inflate filled output buffer. don't change input params yet.
98 } else if (zcontext_.avail_in == 0) {
99 const void* in;
100 int in_size;
101 bool first = zcontext_.next_in == NULL;
102 bool ok = sub_stream_->Next(data: &in, size: &in_size);
103 if (!ok) {
104 zcontext_.next_out = NULL;
105 zcontext_.avail_out = 0;
106 return Z_STREAM_END;
107 }
108 zcontext_.next_in = static_cast<Bytef*>(const_cast<void*>(in));
109 zcontext_.avail_in = in_size;
110 if (first) {
111 int error = internalInflateInit2(zcontext: &zcontext_, format: format_);
112 if (error != Z_OK) {
113 return error;
114 }
115 }
116 }
117 zcontext_.next_out = static_cast<Bytef*>(output_buffer_);
118 zcontext_.avail_out = output_buffer_length_;
119 output_position_ = output_buffer_;
120 int error = inflate(strm: &zcontext_, flush);
121 return error;
122}
123
124void GzipInputStream::DoNextOutput(const void** data, int* size) {
125 *data = output_position_;
126 *size = ((uintptr_t)zcontext_.next_out) - ((uintptr_t)output_position_);
127 output_position_ = zcontext_.next_out;
128}
129
130// implements ZeroCopyInputStream ----------------------------------
131bool GzipInputStream::Next(const void** data, int* size) {
132 bool ok = (zerror_ == Z_OK) || (zerror_ == Z_STREAM_END) ||
133 (zerror_ == Z_BUF_ERROR);
134 if ((!ok) || (zcontext_.next_out == NULL)) {
135 return false;
136 }
137 if (zcontext_.next_out != output_position_) {
138 DoNextOutput(data, size);
139 return true;
140 }
141 if (zerror_ == Z_STREAM_END) {
142 if (zcontext_.next_out != NULL) {
143 // sub_stream_ may have concatenated streams to follow
144 zerror_ = inflateEnd(strm: &zcontext_);
145 byte_count_ += zcontext_.total_out;
146 if (zerror_ != Z_OK) {
147 return false;
148 }
149 zerror_ = internalInflateInit2(zcontext: &zcontext_, format: format_);
150 if (zerror_ != Z_OK) {
151 return false;
152 }
153 } else {
154 *data = NULL;
155 *size = 0;
156 return false;
157 }
158 }
159 zerror_ = Inflate(Z_NO_FLUSH);
160 if ((zerror_ == Z_STREAM_END) && (zcontext_.next_out == NULL)) {
161 // The underlying stream's Next returned false inside Inflate.
162 return false;
163 }
164 ok = (zerror_ == Z_OK) || (zerror_ == Z_STREAM_END) ||
165 (zerror_ == Z_BUF_ERROR);
166 if (!ok) {
167 return false;
168 }
169 DoNextOutput(data, size);
170 return true;
171}
172void GzipInputStream::BackUp(int count) {
173 output_position_ = reinterpret_cast<void*>(
174 reinterpret_cast<uintptr_t>(output_position_) - count);
175}
176bool GzipInputStream::Skip(int count) {
177 const void* data;
178 int size = 0;
179 bool ok = Next(data: &data, size: &size);
180 while (ok && (size < count)) {
181 count -= size;
182 ok = Next(data: &data, size: &size);
183 }
184 if (size > count) {
185 BackUp(count: size - count);
186 }
187 return ok;
188}
189int64_t GzipInputStream::ByteCount() const {
190 int64_t ret = byte_count_ + zcontext_.total_out;
191 if (zcontext_.next_out != NULL && output_position_ != NULL) {
192 ret += reinterpret_cast<uintptr_t>(zcontext_.next_out) -
193 reinterpret_cast<uintptr_t>(output_position_);
194 }
195 return ret;
196}
197
198// =========================================================================
199
200GzipOutputStream::Options::Options()
201 : format(GZIP),
202 buffer_size(kDefaultBufferSize),
203 compression_level(Z_DEFAULT_COMPRESSION),
204 compression_strategy(Z_DEFAULT_STRATEGY) {}
205
206GzipOutputStream::GzipOutputStream(ZeroCopyOutputStream* sub_stream) {
207 Init(sub_stream, options: Options());
208}
209
210GzipOutputStream::GzipOutputStream(ZeroCopyOutputStream* sub_stream,
211 const Options& options) {
212 Init(sub_stream, options);
213}
214
215void GzipOutputStream::Init(ZeroCopyOutputStream* sub_stream,
216 const Options& options) {
217 sub_stream_ = sub_stream;
218 sub_data_ = NULL;
219 sub_data_size_ = 0;
220
221 input_buffer_length_ = options.buffer_size;
222 input_buffer_ = operator new(input_buffer_length_);
223 GOOGLE_CHECK(input_buffer_ != NULL);
224
225 zcontext_.zalloc = Z_NULL;
226 zcontext_.zfree = Z_NULL;
227 zcontext_.opaque = Z_NULL;
228 zcontext_.next_out = NULL;
229 zcontext_.avail_out = 0;
230 zcontext_.total_out = 0;
231 zcontext_.next_in = NULL;
232 zcontext_.avail_in = 0;
233 zcontext_.total_in = 0;
234 zcontext_.msg = NULL;
235 // default to GZIP format
236 int windowBitsFormat = 16;
237 if (options.format == ZLIB) {
238 windowBitsFormat = 0;
239 }
240 zerror_ =
241 deflateInit2(&zcontext_, options.compression_level, Z_DEFLATED,
242 /* windowBits */ 15 | windowBitsFormat,
243 /* memLevel (default) */ 8, options.compression_strategy);
244}
245
246GzipOutputStream::~GzipOutputStream() {
247 Close();
248 internal::SizedDelete(p: input_buffer_, size: input_buffer_length_);
249}
250
251// private
252int GzipOutputStream::Deflate(int flush) {
253 int error = Z_OK;
254 do {
255 if ((sub_data_ == NULL) || (zcontext_.avail_out == 0)) {
256 bool ok = sub_stream_->Next(data: &sub_data_, size: &sub_data_size_);
257 if (!ok) {
258 sub_data_ = NULL;
259 sub_data_size_ = 0;
260 return Z_BUF_ERROR;
261 }
262 GOOGLE_CHECK_GT(sub_data_size_, 0);
263 zcontext_.next_out = static_cast<Bytef*>(sub_data_);
264 zcontext_.avail_out = sub_data_size_;
265 }
266 error = deflate(strm: &zcontext_, flush);
267 } while (error == Z_OK && zcontext_.avail_out == 0);
268 if ((flush == Z_FULL_FLUSH) || (flush == Z_FINISH)) {
269 // Notify lower layer of data.
270 sub_stream_->BackUp(count: zcontext_.avail_out);
271 // We don't own the buffer anymore.
272 sub_data_ = NULL;
273 sub_data_size_ = 0;
274 }
275 return error;
276}
277
278// implements ZeroCopyOutputStream ---------------------------------
279bool GzipOutputStream::Next(void** data, int* size) {
280 if ((zerror_ != Z_OK) && (zerror_ != Z_BUF_ERROR)) {
281 return false;
282 }
283 if (zcontext_.avail_in != 0) {
284 zerror_ = Deflate(Z_NO_FLUSH);
285 if (zerror_ != Z_OK) {
286 return false;
287 }
288 }
289 if (zcontext_.avail_in == 0) {
290 // all input was consumed. reset the buffer.
291 zcontext_.next_in = static_cast<Bytef*>(input_buffer_);
292 zcontext_.avail_in = input_buffer_length_;
293 *data = input_buffer_;
294 *size = input_buffer_length_;
295 } else {
296 // The loop in Deflate should consume all avail_in
297 GOOGLE_LOG(DFATAL) << "Deflate left bytes unconsumed";
298 }
299 return true;
300}
301void GzipOutputStream::BackUp(int count) {
302 GOOGLE_CHECK_GE(zcontext_.avail_in, static_cast<uInt>(count));
303 zcontext_.avail_in -= count;
304}
305int64_t GzipOutputStream::ByteCount() const {
306 return zcontext_.total_in + zcontext_.avail_in;
307}
308
309bool GzipOutputStream::Flush() {
310 zerror_ = Deflate(Z_FULL_FLUSH);
311 // Return true if the flush succeeded or if it was a no-op.
312 return (zerror_ == Z_OK) ||
313 (zerror_ == Z_BUF_ERROR && zcontext_.avail_in == 0 &&
314 zcontext_.avail_out != 0);
315}
316
317bool GzipOutputStream::Close() {
318 if ((zerror_ != Z_OK) && (zerror_ != Z_BUF_ERROR)) {
319 return false;
320 }
321 do {
322 zerror_ = Deflate(Z_FINISH);
323 } while (zerror_ == Z_OK);
324 zerror_ = deflateEnd(strm: &zcontext_);
325 bool ok = zerror_ == Z_OK;
326 zerror_ = Z_STREAM_END;
327 return ok;
328}
329
330} // namespace io
331} // namespace protobuf
332} // namespace google
333
334#endif // HAVE_ZLIB
335