GoogleToolboxSubset/GTMNSData+zlib.m
author Jens Alfke <jens@mooseyard.com>
Sat Mar 28 09:39:31 2009 -0700 (2009-03-28)
changeset 21 88d7e2455a7f
permissions -rw-r--r--
(merge)
     1 //
     2 //  GTMNSData+zlib.m
     3 //
     4 //  Copyright 2007-2008 Google Inc.
     5 //
     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
     8 //  of the License at
     9 // 
    10 //  http://www.apache.org/licenses/LICENSE-2.0
    11 // 
    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
    16 //  the License.
    17 //
    18 
    19 #import "GTMNSData+zlib.h"
    20 #import <zlib.h>
    21 #import "GTMDefines.h"
    22 
    23 #define kChunkSize 1024
    24 
    25 @interface NSData (GTMZlibAdditionsPrivate)
    26 + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
    27                                 length:(NSUInteger)length
    28                       compressionLevel:(int)level
    29                                useGzip:(BOOL)useGzip;
    30 @end
    31 
    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) {
    38     return nil;
    39   }
    40   
    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
    44   // at the moment.
    45   _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
    46 
    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) {
    51     level = Z_BEST_SPEED;
    52   } else if (level > Z_BEST_COMPRESSION) {
    53     level = Z_BEST_COMPRESSION;
    54   }
    55 
    56   z_stream strm;
    57   bzero(&strm, sizeof(z_stream));
    58 
    59   int windowBits = 15; // the default
    60   int memLevel = 8; // the default
    61   if (useGzip) {
    62     windowBits += 16; // enable gzip header instead of zlib header
    63   }
    64   int retCode;
    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",
    69                level, retCode);
    70     return nil;
    71     // COV_NF_END
    72   }
    73 
    74   // hint the size at 1/4 the input size
    75   NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)];
    76   unsigned char output[kChunkSize];
    77 
    78   // setup the input
    79   strm.avail_in = (unsigned int)length;
    80   strm.next_in = (unsigned char*)bytes;
    81 
    82   // loop to collect the data
    83   do {
    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
    92       // way to test it)
    93       _GTMDevLog(@"Error trying to deflate some of the payload, error %d",
    94                  retCode);
    95       deflateEnd(&strm);
    96       return nil;
    97       // COV_NF_END
    98     }
    99     // collect what we got
   100     unsigned gotBack = kChunkSize - strm.avail_out;
   101     if (gotBack > 0) {
   102       [result appendBytes:output length:gotBack];
   103     }
   104 
   105   } while (retCode == Z_OK);
   106 
   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",
   110                 strm.avail_in);
   111   _GTMDevAssert(retCode == Z_STREAM_END,
   112                 @"thought we finished deflate w/o getting a result of stream end, code %d",
   113                 retCode);
   114 
   115   // clean up
   116   deflateEnd(&strm);
   117 
   118   return result;
   119 } // gtm_dataByCompressingBytes:length:compressionLevel:useGzip:
   120   
   121 
   122 @end
   123 
   124 
   125 @implementation NSData (GTMZLibAdditions)
   126 
   127 + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
   128                              length:(NSUInteger)length {
   129   return [self gtm_dataByCompressingBytes:bytes
   130                                    length:length
   131                          compressionLevel:Z_DEFAULT_COMPRESSION
   132                                   useGzip:YES];
   133 } // gtm_dataByGzippingBytes:length:
   134 
   135 + (NSData *)gtm_dataByGzippingData:(NSData *)data {
   136   return [self gtm_dataByCompressingBytes:[data bytes]
   137                                    length:[data length]
   138                          compressionLevel:Z_DEFAULT_COMPRESSION
   139                                   useGzip:YES];
   140 } // gtm_dataByGzippingData:
   141 
   142 + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
   143                              length:(NSUInteger)length
   144                    compressionLevel:(int)level {
   145   return [self gtm_dataByCompressingBytes:bytes
   146                                    length:length
   147                          compressionLevel:level
   148                                   useGzip:YES];
   149 } // gtm_dataByGzippingBytes:length:level:
   150 
   151 + (NSData *)gtm_dataByGzippingData:(NSData *)data
   152                   compressionLevel:(int)level {
   153   return [self gtm_dataByCompressingBytes:[data bytes]
   154                                    length:[data length]
   155                          compressionLevel:level
   156                                   useGzip:YES];
   157 } // gtm_dataByGzippingData:level:
   158 
   159 + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
   160                               length:(NSUInteger)length {
   161   return [self gtm_dataByCompressingBytes:bytes
   162                                    length:length
   163                          compressionLevel:Z_DEFAULT_COMPRESSION
   164                                   useGzip:NO];
   165 } // gtm_dataByDeflatingBytes:length:
   166 
   167 + (NSData *)gtm_dataByDeflatingData:(NSData *)data {
   168   return [self gtm_dataByCompressingBytes:[data bytes]
   169                                    length:[data length]
   170                          compressionLevel:Z_DEFAULT_COMPRESSION
   171                                   useGzip:NO];
   172 } // gtm_dataByDeflatingData:
   173 
   174 + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
   175                               length:(NSUInteger)length
   176                     compressionLevel:(int)level {
   177   return [self gtm_dataByCompressingBytes:bytes
   178                                    length:length
   179                          compressionLevel:level
   180                                   useGzip:NO];
   181 } // gtm_dataByDeflatingBytes:length:level:
   182 
   183 + (NSData *)gtm_dataByDeflatingData:(NSData *)data
   184                    compressionLevel:(int)level {
   185   return [self gtm_dataByCompressingBytes:[data bytes]
   186                                    length:[data length]
   187                          compressionLevel:level
   188                                   useGzip:NO];
   189 } // gtm_dataByDeflatingData:level:
   190 
   191 + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes
   192                               length:(NSUInteger)length {
   193   if (!bytes || !length) {
   194     return nil;
   195   }
   196   
   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
   200   // at the moment.
   201   _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
   202 
   203   z_stream strm;
   204   bzero(&strm, sizeof(z_stream));
   205 
   206   // setup the input
   207   strm.avail_in = (unsigned int)length;
   208   strm.next_in = (unsigned char*)bytes;
   209 
   210   int windowBits = 15; // 15 to enable any window size
   211   windowBits += 32; // and +32 to enable zlib or gzip header detection.
   212   int retCode;
   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);
   216     return nil;
   217     // COV_NF_END
   218   }
   219 
   220   // hint the size at 4x the input size
   221   NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)];
   222   unsigned char output[kChunkSize];
   223 
   224   // loop to collect the data
   225   do {
   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",
   232                  retCode);
   233       inflateEnd(&strm);
   234       return nil;
   235     }
   236     // collect what we got
   237     unsigned gotBack = kChunkSize - strm.avail_out;
   238     if (gotBack > 0) {
   239       [result appendBytes:output length:gotBack];
   240     }
   241 
   242   } while (retCode == Z_OK);
   243 
   244   // make sure there wasn't more data tacked onto the end of a valid compressed
   245   // stream.
   246   if (strm.avail_in != 0) {
   247     _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left",
   248                strm.avail_in);
   249     result = nil;
   250   }
   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",
   254                 retCode);
   255 
   256   // clean up
   257   inflateEnd(&strm);
   258 
   259   return result;
   260 } // gtm_dataByInflatingBytes:length:
   261 
   262 + (NSData *)gtm_dataByInflatingData:(NSData *)data {
   263   return [self gtm_dataByInflatingBytes:[data bytes]
   264                                  length:[data length]];
   265 } // gtm_dataByInflatingData:
   266 
   267 @end