4 // Copyright 2007-2008 Google Inc.
6 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
7 // use this file except in compliance with the License. You may obtain a copy
10 // http://www.apache.org/licenses/LICENSE-2.0
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 // License for the specific language governing permissions and limitations under
19 #import "GTMNSData+zlib.h"
21 #import "GTMDefines.h"
23 #define kChunkSize 1024
25 @interface NSData (GTMZlibAdditionsPrivate)
26 + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
27 length:(NSUInteger)length
28 compressionLevel:(int)level
29 useGzip:(BOOL)useGzip;
32 @implementation NSData (GTMZlibAdditionsPrivate)
33 + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
34 length:(NSUInteger)length
35 compressionLevel:(int)level
36 useGzip:(BOOL)useGzip {
37 if (!bytes || !length) {
41 // TODO: support 64bit inputs
42 // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
43 // feeding the data until we've gotten it all in. not supporting this
45 _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
47 if (level == Z_DEFAULT_COMPRESSION) {
48 // the default value is actually outside the range, so we have to let it
49 // through specifically.
50 } else if (level < Z_BEST_SPEED) {
52 } else if (level > Z_BEST_COMPRESSION) {
53 level = Z_BEST_COMPRESSION;
57 bzero(&strm, sizeof(z_stream));
59 int windowBits = 15; // the default
60 int memLevel = 8; // the default
62 windowBits += 16; // enable gzip header instead of zlib header
65 if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits,
66 memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) {
67 // COV_NF_START - no real way to force this in a unittest (we guard all args)
68 _GTMDevLog(@"Failed to init for deflate w/ level %d, error %d",
74 // hint the size at 1/4 the input size
75 NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)];
76 unsigned char output[kChunkSize];
79 strm.avail_in = (unsigned int)length;
80 strm.next_in = (unsigned char*)bytes;
82 // loop to collect the data
84 // update what we're passing in
85 strm.avail_out = kChunkSize;
86 strm.next_out = output;
87 retCode = deflate(&strm, Z_FINISH);
88 if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
89 // COV_NF_START - no real way to force this in a unittest
90 // (in inflate, we can feed bogus/truncated data to test, but an error
91 // here would be some internal issue w/in zlib, and there isn't any real
93 _GTMDevLog(@"Error trying to deflate some of the payload, error %d",
99 // collect what we got
100 unsigned gotBack = kChunkSize - strm.avail_out;
102 [result appendBytes:output length:gotBack];
105 } while (retCode == Z_OK);
107 // if the loop exits, we used all input and the stream ended
108 _GTMDevAssert(strm.avail_in == 0,
109 @"thought we finished deflate w/o using all input, %u bytes left",
111 _GTMDevAssert(retCode == Z_STREAM_END,
112 @"thought we finished deflate w/o getting a result of stream end, code %d",
119 } // gtm_dataByCompressingBytes:length:compressionLevel:useGzip:
125 @implementation NSData (GTMZLibAdditions)
127 + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
128 length:(NSUInteger)length {
129 return [self gtm_dataByCompressingBytes:bytes
131 compressionLevel:Z_DEFAULT_COMPRESSION
133 } // gtm_dataByGzippingBytes:length:
135 + (NSData *)gtm_dataByGzippingData:(NSData *)data {
136 return [self gtm_dataByCompressingBytes:[data bytes]
138 compressionLevel:Z_DEFAULT_COMPRESSION
140 } // gtm_dataByGzippingData:
142 + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
143 length:(NSUInteger)length
144 compressionLevel:(int)level {
145 return [self gtm_dataByCompressingBytes:bytes
147 compressionLevel:level
149 } // gtm_dataByGzippingBytes:length:level:
151 + (NSData *)gtm_dataByGzippingData:(NSData *)data
152 compressionLevel:(int)level {
153 return [self gtm_dataByCompressingBytes:[data bytes]
155 compressionLevel:level
157 } // gtm_dataByGzippingData:level:
159 + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
160 length:(NSUInteger)length {
161 return [self gtm_dataByCompressingBytes:bytes
163 compressionLevel:Z_DEFAULT_COMPRESSION
165 } // gtm_dataByDeflatingBytes:length:
167 + (NSData *)gtm_dataByDeflatingData:(NSData *)data {
168 return [self gtm_dataByCompressingBytes:[data bytes]
170 compressionLevel:Z_DEFAULT_COMPRESSION
172 } // gtm_dataByDeflatingData:
174 + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
175 length:(NSUInteger)length
176 compressionLevel:(int)level {
177 return [self gtm_dataByCompressingBytes:bytes
179 compressionLevel:level
181 } // gtm_dataByDeflatingBytes:length:level:
183 + (NSData *)gtm_dataByDeflatingData:(NSData *)data
184 compressionLevel:(int)level {
185 return [self gtm_dataByCompressingBytes:[data bytes]
187 compressionLevel:level
189 } // gtm_dataByDeflatingData:level:
191 + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes
192 length:(NSUInteger)length {
193 if (!bytes || !length) {
197 // TODO: support 64bit inputs
198 // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
199 // feeding the data until we've gotten it all in. not supporting this
201 _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
204 bzero(&strm, sizeof(z_stream));
207 strm.avail_in = (unsigned int)length;
208 strm.next_in = (unsigned char*)bytes;
210 int windowBits = 15; // 15 to enable any window size
211 windowBits += 32; // and +32 to enable zlib or gzip header detection.
213 if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) {
214 // COV_NF_START - no real way to force this in a unittest (we guard all args)
215 _GTMDevLog(@"Failed to init for inflate, error %d", retCode);
220 // hint the size at 4x the input size
221 NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)];
222 unsigned char output[kChunkSize];
224 // loop to collect the data
226 // update what we're passing in
227 strm.avail_out = kChunkSize;
228 strm.next_out = output;
229 retCode = inflate(&strm, Z_NO_FLUSH);
230 if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
231 _GTMDevLog(@"Error trying to inflate some of the payload, error %d",
236 // collect what we got
237 unsigned gotBack = kChunkSize - strm.avail_out;
239 [result appendBytes:output length:gotBack];
242 } while (retCode == Z_OK);
244 // make sure there wasn't more data tacked onto the end of a valid compressed
246 if (strm.avail_in != 0) {
247 _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left",
251 // the only way out of the loop was by hitting the end of the stream
252 _GTMDevAssert(retCode == Z_STREAM_END,
253 @"thought we finished inflate w/o getting a result of stream end, code %d",
260 } // gtm_dataByInflatingBytes:length:
262 + (NSData *)gtm_dataByInflatingData:(NSData *)data {
263 return [self gtm_dataByInflatingBytes:[data bytes]
264 length:[data length]];
265 } // gtm_dataByInflatingData: