1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/GoogleToolboxSubset/GTMNSData+zlib.m Sun May 03 10:13:31 2009 -0700
1.3 @@ -0,0 +1,267 @@
1.4 +//
1.5 +// GTMNSData+zlib.m
1.6 +//
1.7 +// Copyright 2007-2008 Google Inc.
1.8 +//
1.9 +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
1.10 +// use this file except in compliance with the License. You may obtain a copy
1.11 +// of the License at
1.12 +//
1.13 +// http://www.apache.org/licenses/LICENSE-2.0
1.14 +//
1.15 +// Unless required by applicable law or agreed to in writing, software
1.16 +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1.17 +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1.18 +// License for the specific language governing permissions and limitations under
1.19 +// the License.
1.20 +//
1.21 +
1.22 +#import "GTMNSData+zlib.h"
1.23 +#import <zlib.h>
1.24 +#import "GTMDefines.h"
1.25 +
1.26 +#define kChunkSize 1024
1.27 +
1.28 +@interface NSData (GTMZlibAdditionsPrivate)
1.29 ++ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
1.30 + length:(NSUInteger)length
1.31 + compressionLevel:(int)level
1.32 + useGzip:(BOOL)useGzip;
1.33 +@end
1.34 +
1.35 +@implementation NSData (GTMZlibAdditionsPrivate)
1.36 ++ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
1.37 + length:(NSUInteger)length
1.38 + compressionLevel:(int)level
1.39 + useGzip:(BOOL)useGzip {
1.40 + if (!bytes || !length) {
1.41 + return nil;
1.42 + }
1.43 +
1.44 + // TODO: support 64bit inputs
1.45 + // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
1.46 + // feeding the data until we've gotten it all in. not supporting this
1.47 + // at the moment.
1.48 + _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
1.49 +
1.50 + if (level == Z_DEFAULT_COMPRESSION) {
1.51 + // the default value is actually outside the range, so we have to let it
1.52 + // through specifically.
1.53 + } else if (level < Z_BEST_SPEED) {
1.54 + level = Z_BEST_SPEED;
1.55 + } else if (level > Z_BEST_COMPRESSION) {
1.56 + level = Z_BEST_COMPRESSION;
1.57 + }
1.58 +
1.59 + z_stream strm;
1.60 + bzero(&strm, sizeof(z_stream));
1.61 +
1.62 + int windowBits = 15; // the default
1.63 + int memLevel = 8; // the default
1.64 + if (useGzip) {
1.65 + windowBits += 16; // enable gzip header instead of zlib header
1.66 + }
1.67 + int retCode;
1.68 + if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits,
1.69 + memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) {
1.70 + // COV_NF_START - no real way to force this in a unittest (we guard all args)
1.71 + _GTMDevLog(@"Failed to init for deflate w/ level %d, error %d",
1.72 + level, retCode);
1.73 + return nil;
1.74 + // COV_NF_END
1.75 + }
1.76 +
1.77 + // hint the size at 1/4 the input size
1.78 + NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)];
1.79 + unsigned char output[kChunkSize];
1.80 +
1.81 + // setup the input
1.82 + strm.avail_in = (unsigned int)length;
1.83 + strm.next_in = (unsigned char*)bytes;
1.84 +
1.85 + // loop to collect the data
1.86 + do {
1.87 + // update what we're passing in
1.88 + strm.avail_out = kChunkSize;
1.89 + strm.next_out = output;
1.90 + retCode = deflate(&strm, Z_FINISH);
1.91 + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
1.92 + // COV_NF_START - no real way to force this in a unittest
1.93 + // (in inflate, we can feed bogus/truncated data to test, but an error
1.94 + // here would be some internal issue w/in zlib, and there isn't any real
1.95 + // way to test it)
1.96 + _GTMDevLog(@"Error trying to deflate some of the payload, error %d",
1.97 + retCode);
1.98 + deflateEnd(&strm);
1.99 + return nil;
1.100 + // COV_NF_END
1.101 + }
1.102 + // collect what we got
1.103 + unsigned gotBack = kChunkSize - strm.avail_out;
1.104 + if (gotBack > 0) {
1.105 + [result appendBytes:output length:gotBack];
1.106 + }
1.107 +
1.108 + } while (retCode == Z_OK);
1.109 +
1.110 + // if the loop exits, we used all input and the stream ended
1.111 + _GTMDevAssert(strm.avail_in == 0,
1.112 + @"thought we finished deflate w/o using all input, %u bytes left",
1.113 + strm.avail_in);
1.114 + _GTMDevAssert(retCode == Z_STREAM_END,
1.115 + @"thought we finished deflate w/o getting a result of stream end, code %d",
1.116 + retCode);
1.117 +
1.118 + // clean up
1.119 + deflateEnd(&strm);
1.120 +
1.121 + return result;
1.122 +} // gtm_dataByCompressingBytes:length:compressionLevel:useGzip:
1.123 +
1.124 +
1.125 +@end
1.126 +
1.127 +
1.128 +@implementation NSData (GTMZLibAdditions)
1.129 +
1.130 ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
1.131 + length:(NSUInteger)length {
1.132 + return [self gtm_dataByCompressingBytes:bytes
1.133 + length:length
1.134 + compressionLevel:Z_DEFAULT_COMPRESSION
1.135 + useGzip:YES];
1.136 +} // gtm_dataByGzippingBytes:length:
1.137 +
1.138 ++ (NSData *)gtm_dataByGzippingData:(NSData *)data {
1.139 + return [self gtm_dataByCompressingBytes:[data bytes]
1.140 + length:[data length]
1.141 + compressionLevel:Z_DEFAULT_COMPRESSION
1.142 + useGzip:YES];
1.143 +} // gtm_dataByGzippingData:
1.144 +
1.145 ++ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
1.146 + length:(NSUInteger)length
1.147 + compressionLevel:(int)level {
1.148 + return [self gtm_dataByCompressingBytes:bytes
1.149 + length:length
1.150 + compressionLevel:level
1.151 + useGzip:YES];
1.152 +} // gtm_dataByGzippingBytes:length:level:
1.153 +
1.154 ++ (NSData *)gtm_dataByGzippingData:(NSData *)data
1.155 + compressionLevel:(int)level {
1.156 + return [self gtm_dataByCompressingBytes:[data bytes]
1.157 + length:[data length]
1.158 + compressionLevel:level
1.159 + useGzip:YES];
1.160 +} // gtm_dataByGzippingData:level:
1.161 +
1.162 ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
1.163 + length:(NSUInteger)length {
1.164 + return [self gtm_dataByCompressingBytes:bytes
1.165 + length:length
1.166 + compressionLevel:Z_DEFAULT_COMPRESSION
1.167 + useGzip:NO];
1.168 +} // gtm_dataByDeflatingBytes:length:
1.169 +
1.170 ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data {
1.171 + return [self gtm_dataByCompressingBytes:[data bytes]
1.172 + length:[data length]
1.173 + compressionLevel:Z_DEFAULT_COMPRESSION
1.174 + useGzip:NO];
1.175 +} // gtm_dataByDeflatingData:
1.176 +
1.177 ++ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
1.178 + length:(NSUInteger)length
1.179 + compressionLevel:(int)level {
1.180 + return [self gtm_dataByCompressingBytes:bytes
1.181 + length:length
1.182 + compressionLevel:level
1.183 + useGzip:NO];
1.184 +} // gtm_dataByDeflatingBytes:length:level:
1.185 +
1.186 ++ (NSData *)gtm_dataByDeflatingData:(NSData *)data
1.187 + compressionLevel:(int)level {
1.188 + return [self gtm_dataByCompressingBytes:[data bytes]
1.189 + length:[data length]
1.190 + compressionLevel:level
1.191 + useGzip:NO];
1.192 +} // gtm_dataByDeflatingData:level:
1.193 +
1.194 ++ (NSData *)gtm_dataByInflatingBytes:(const void *)bytes
1.195 + length:(NSUInteger)length {
1.196 + if (!bytes || !length) {
1.197 + return nil;
1.198 + }
1.199 +
1.200 + // TODO: support 64bit inputs
1.201 + // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
1.202 + // feeding the data until we've gotten it all in. not supporting this
1.203 + // at the moment.
1.204 + _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
1.205 +
1.206 + z_stream strm;
1.207 + bzero(&strm, sizeof(z_stream));
1.208 +
1.209 + // setup the input
1.210 + strm.avail_in = (unsigned int)length;
1.211 + strm.next_in = (unsigned char*)bytes;
1.212 +
1.213 + int windowBits = 15; // 15 to enable any window size
1.214 + windowBits += 32; // and +32 to enable zlib or gzip header detection.
1.215 + int retCode;
1.216 + if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) {
1.217 + // COV_NF_START - no real way to force this in a unittest (we guard all args)
1.218 + _GTMDevLog(@"Failed to init for inflate, error %d", retCode);
1.219 + return nil;
1.220 + // COV_NF_END
1.221 + }
1.222 +
1.223 + // hint the size at 4x the input size
1.224 + NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)];
1.225 + unsigned char output[kChunkSize];
1.226 +
1.227 + // loop to collect the data
1.228 + do {
1.229 + // update what we're passing in
1.230 + strm.avail_out = kChunkSize;
1.231 + strm.next_out = output;
1.232 + retCode = inflate(&strm, Z_NO_FLUSH);
1.233 + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
1.234 + _GTMDevLog(@"Error trying to inflate some of the payload, error %d",
1.235 + retCode);
1.236 + inflateEnd(&strm);
1.237 + return nil;
1.238 + }
1.239 + // collect what we got
1.240 + unsigned gotBack = kChunkSize - strm.avail_out;
1.241 + if (gotBack > 0) {
1.242 + [result appendBytes:output length:gotBack];
1.243 + }
1.244 +
1.245 + } while (retCode == Z_OK);
1.246 +
1.247 + // make sure there wasn't more data tacked onto the end of a valid compressed
1.248 + // stream.
1.249 + if (strm.avail_in != 0) {
1.250 + _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left",
1.251 + strm.avail_in);
1.252 + result = nil;
1.253 + }
1.254 + // the only way out of the loop was by hitting the end of the stream
1.255 + _GTMDevAssert(retCode == Z_STREAM_END,
1.256 + @"thought we finished inflate w/o getting a result of stream end, code %d",
1.257 + retCode);
1.258 +
1.259 + // clean up
1.260 + inflateEnd(&strm);
1.261 +
1.262 + return result;
1.263 +} // gtm_dataByInflatingBytes:length:
1.264 +
1.265 ++ (NSData *)gtm_dataByInflatingData:(NSData *)data {
1.266 + return [self gtm_dataByInflatingBytes:[data bytes]
1.267 + length:[data length]];
1.268 +} // gtm_dataByInflatingData:
1.269 +
1.270 +@end