jens@13: // jens@13: // GTMNSData+zlib.m jens@13: // jens@13: // Copyright 2007-2008 Google Inc. jens@13: // jens@13: // Licensed under the Apache License, Version 2.0 (the "License"); you may not jens@13: // use this file except in compliance with the License. You may obtain a copy jens@13: // of the License at jens@13: // jens@13: // http://www.apache.org/licenses/LICENSE-2.0 jens@13: // jens@13: // Unless required by applicable law or agreed to in writing, software jens@13: // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT jens@13: // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the jens@13: // License for the specific language governing permissions and limitations under jens@13: // the License. jens@13: // jens@13: jens@13: #import "GTMNSData+zlib.h" jens@13: #import jens@13: #import "GTMDefines.h" jens@13: jens@13: #define kChunkSize 1024 jens@13: jens@13: @interface NSData (GTMZlibAdditionsPrivate) jens@13: + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes jens@13: length:(NSUInteger)length jens@13: compressionLevel:(int)level jens@13: useGzip:(BOOL)useGzip; jens@13: @end jens@13: jens@13: @implementation NSData (GTMZlibAdditionsPrivate) jens@13: + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes jens@13: length:(NSUInteger)length jens@13: compressionLevel:(int)level jens@13: useGzip:(BOOL)useGzip { jens@13: if (!bytes || !length) { jens@13: return nil; jens@13: } jens@13: jens@13: // TODO: support 64bit inputs jens@13: // avail_in is a uInt, so if length > UINT_MAX we actually need to loop jens@13: // feeding the data until we've gotten it all in. not supporting this jens@13: // at the moment. jens@13: _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths"); jens@13: jens@13: if (level == Z_DEFAULT_COMPRESSION) { jens@13: // the default value is actually outside the range, so we have to let it jens@13: // through specifically. jens@13: } else if (level < Z_BEST_SPEED) { jens@13: level = Z_BEST_SPEED; jens@13: } else if (level > Z_BEST_COMPRESSION) { jens@13: level = Z_BEST_COMPRESSION; jens@13: } jens@13: jens@13: z_stream strm; jens@13: bzero(&strm, sizeof(z_stream)); jens@13: jens@13: int windowBits = 15; // the default jens@13: int memLevel = 8; // the default jens@13: if (useGzip) { jens@13: windowBits += 16; // enable gzip header instead of zlib header jens@13: } jens@13: int retCode; jens@13: if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, jens@13: memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) { jens@13: // COV_NF_START - no real way to force this in a unittest (we guard all args) jens@13: _GTMDevLog(@"Failed to init for deflate w/ level %d, error %d", jens@13: level, retCode); jens@13: return nil; jens@13: // COV_NF_END jens@13: } jens@13: jens@13: // hint the size at 1/4 the input size jens@13: NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)]; jens@13: unsigned char output[kChunkSize]; jens@13: jens@13: // setup the input jens@13: strm.avail_in = (unsigned int)length; jens@13: strm.next_in = (unsigned char*)bytes; jens@13: jens@13: // loop to collect the data jens@13: do { jens@13: // update what we're passing in jens@13: strm.avail_out = kChunkSize; jens@13: strm.next_out = output; jens@13: retCode = deflate(&strm, Z_FINISH); jens@13: if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { jens@13: // COV_NF_START - no real way to force this in a unittest jens@13: // (in inflate, we can feed bogus/truncated data to test, but an error jens@13: // here would be some internal issue w/in zlib, and there isn't any real jens@13: // way to test it) jens@13: _GTMDevLog(@"Error trying to deflate some of the payload, error %d", jens@13: retCode); jens@13: deflateEnd(&strm); jens@13: return nil; jens@13: // COV_NF_END jens@13: } jens@13: // collect what we got jens@13: unsigned gotBack = kChunkSize - strm.avail_out; jens@13: if (gotBack > 0) { jens@13: [result appendBytes:output length:gotBack]; jens@13: } jens@13: jens@13: } while (retCode == Z_OK); jens@13: jens@13: // if the loop exits, we used all input and the stream ended jens@13: _GTMDevAssert(strm.avail_in == 0, jens@13: @"thought we finished deflate w/o using all input, %u bytes left", jens@13: strm.avail_in); jens@13: _GTMDevAssert(retCode == Z_STREAM_END, jens@13: @"thought we finished deflate w/o getting a result of stream end, code %d", jens@13: retCode); jens@13: jens@13: // clean up jens@13: deflateEnd(&strm); jens@13: jens@13: return result; jens@13: } // gtm_dataByCompressingBytes:length:compressionLevel:useGzip: jens@13: jens@13: jens@13: @end jens@13: jens@13: jens@13: @implementation NSData (GTMZLibAdditions) jens@13: jens@13: + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes jens@13: length:(NSUInteger)length { jens@13: return [self gtm_dataByCompressingBytes:bytes jens@13: length:length jens@13: compressionLevel:Z_DEFAULT_COMPRESSION jens@13: useGzip:YES]; jens@13: } // gtm_dataByGzippingBytes:length: jens@13: jens@13: + (NSData *)gtm_dataByGzippingData:(NSData *)data { jens@13: return [self gtm_dataByCompressingBytes:[data bytes] jens@13: length:[data length] jens@13: compressionLevel:Z_DEFAULT_COMPRESSION jens@13: useGzip:YES]; jens@13: } // gtm_dataByGzippingData: jens@13: jens@13: + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes jens@13: length:(NSUInteger)length jens@13: compressionLevel:(int)level { jens@13: return [self gtm_dataByCompressingBytes:bytes jens@13: length:length jens@13: compressionLevel:level jens@13: useGzip:YES]; jens@13: } // gtm_dataByGzippingBytes:length:level: jens@13: jens@13: + (NSData *)gtm_dataByGzippingData:(NSData *)data jens@13: compressionLevel:(int)level { jens@13: return [self gtm_dataByCompressingBytes:[data bytes] jens@13: length:[data length] jens@13: compressionLevel:level jens@13: useGzip:YES]; jens@13: } // gtm_dataByGzippingData:level: jens@13: jens@13: + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes jens@13: length:(NSUInteger)length { jens@13: return [self gtm_dataByCompressingBytes:bytes jens@13: length:length jens@13: compressionLevel:Z_DEFAULT_COMPRESSION jens@13: useGzip:NO]; jens@13: } // gtm_dataByDeflatingBytes:length: jens@13: jens@13: + (NSData *)gtm_dataByDeflatingData:(NSData *)data { jens@13: return [self gtm_dataByCompressingBytes:[data bytes] jens@13: length:[data length] jens@13: compressionLevel:Z_DEFAULT_COMPRESSION jens@13: useGzip:NO]; jens@13: } // gtm_dataByDeflatingData: jens@13: jens@13: + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes jens@13: length:(NSUInteger)length jens@13: compressionLevel:(int)level { jens@13: return [self gtm_dataByCompressingBytes:bytes jens@13: length:length jens@13: compressionLevel:level jens@13: useGzip:NO]; jens@13: } // gtm_dataByDeflatingBytes:length:level: jens@13: jens@13: + (NSData *)gtm_dataByDeflatingData:(NSData *)data jens@13: compressionLevel:(int)level { jens@13: return [self gtm_dataByCompressingBytes:[data bytes] jens@13: length:[data length] jens@13: compressionLevel:level jens@13: useGzip:NO]; jens@13: } // gtm_dataByDeflatingData:level: jens@13: jens@13: + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes jens@13: length:(NSUInteger)length { jens@13: if (!bytes || !length) { jens@13: return nil; jens@13: } jens@13: jens@13: // TODO: support 64bit inputs jens@13: // avail_in is a uInt, so if length > UINT_MAX we actually need to loop jens@13: // feeding the data until we've gotten it all in. not supporting this jens@13: // at the moment. jens@13: _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths"); jens@13: jens@13: z_stream strm; jens@13: bzero(&strm, sizeof(z_stream)); jens@13: jens@13: // setup the input jens@13: strm.avail_in = (unsigned int)length; jens@13: strm.next_in = (unsigned char*)bytes; jens@13: jens@13: int windowBits = 15; // 15 to enable any window size jens@13: windowBits += 32; // and +32 to enable zlib or gzip header detection. jens@13: int retCode; jens@13: if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { jens@13: // COV_NF_START - no real way to force this in a unittest (we guard all args) jens@13: _GTMDevLog(@"Failed to init for inflate, error %d", retCode); jens@13: return nil; jens@13: // COV_NF_END jens@13: } jens@13: jens@13: // hint the size at 4x the input size jens@13: NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)]; jens@13: unsigned char output[kChunkSize]; jens@13: jens@13: // loop to collect the data jens@13: do { jens@13: // update what we're passing in jens@13: strm.avail_out = kChunkSize; jens@13: strm.next_out = output; jens@13: retCode = inflate(&strm, Z_NO_FLUSH); jens@13: if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { jens@13: _GTMDevLog(@"Error trying to inflate some of the payload, error %d", jens@13: retCode); jens@13: inflateEnd(&strm); jens@13: return nil; jens@13: } jens@13: // collect what we got jens@13: unsigned gotBack = kChunkSize - strm.avail_out; jens@13: if (gotBack > 0) { jens@13: [result appendBytes:output length:gotBack]; jens@13: } jens@13: jens@13: } while (retCode == Z_OK); jens@13: jens@13: // make sure there wasn't more data tacked onto the end of a valid compressed jens@13: // stream. jens@13: if (strm.avail_in != 0) { jens@13: _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left", jens@13: strm.avail_in); jens@13: result = nil; jens@13: } jens@13: // the only way out of the loop was by hitting the end of the stream jens@13: _GTMDevAssert(retCode == Z_STREAM_END, jens@13: @"thought we finished inflate w/o getting a result of stream end, code %d", jens@13: retCode); jens@13: jens@13: // clean up jens@13: inflateEnd(&strm); jens@13: jens@13: return result; jens@13: } // gtm_dataByInflatingBytes:length: jens@13: jens@13: + (NSData *)gtm_dataByInflatingData:(NSData *)data { jens@13: return [self gtm_dataByInflatingBytes:[data bytes] jens@13: length:[data length]]; jens@13: } // gtm_dataByInflatingData: jens@13: jens@13: @end