GoogleToolboxSubset/GTMNSData+zlib.m
author Jens Alfke <jens@mooseyard.com>
Mon Aug 10 08:29:32 2009 -0700 (2009-08-10)
changeset 34 50c4f26bcc1b
permissions -rw-r--r--
Fixed signed/unsigned warnings in Base64.m.
jens@13
     1
//
jens@13
     2
//  GTMNSData+zlib.m
jens@13
     3
//
jens@13
     4
//  Copyright 2007-2008 Google Inc.
jens@13
     5
//
jens@13
     6
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
jens@13
     7
//  use this file except in compliance with the License.  You may obtain a copy
jens@13
     8
//  of the License at
jens@13
     9
// 
jens@13
    10
//  http://www.apache.org/licenses/LICENSE-2.0
jens@13
    11
// 
jens@13
    12
//  Unless required by applicable law or agreed to in writing, software
jens@13
    13
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
jens@13
    14
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
jens@13
    15
//  License for the specific language governing permissions and limitations under
jens@13
    16
//  the License.
jens@13
    17
//
jens@13
    18
jens@13
    19
#import "GTMNSData+zlib.h"
jens@13
    20
#import <zlib.h>
jens@13
    21
#import "GTMDefines.h"
jens@13
    22
jens@13
    23
#define kChunkSize 1024
jens@13
    24
jens@13
    25
@interface NSData (GTMZlibAdditionsPrivate)
jens@13
    26
+ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
jens@13
    27
                                length:(NSUInteger)length
jens@13
    28
                      compressionLevel:(int)level
jens@13
    29
                               useGzip:(BOOL)useGzip;
jens@13
    30
@end
jens@13
    31
jens@13
    32
@implementation NSData (GTMZlibAdditionsPrivate)
jens@13
    33
+ (NSData *)gtm_dataByCompressingBytes:(const void *)bytes
jens@13
    34
                                length:(NSUInteger)length
jens@13
    35
                      compressionLevel:(int)level
jens@13
    36
                               useGzip:(BOOL)useGzip {
jens@13
    37
  if (!bytes || !length) {
jens@13
    38
    return nil;
jens@13
    39
  }
jens@13
    40
  
jens@13
    41
  // TODO: support 64bit inputs
jens@13
    42
  // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
jens@13
    43
  // feeding the data until we've gotten it all in.  not supporting this
jens@13
    44
  // at the moment.
jens@13
    45
  _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
jens@13
    46
jens@13
    47
  if (level == Z_DEFAULT_COMPRESSION) {
jens@13
    48
    // the default value is actually outside the range, so we have to let it
jens@13
    49
    // through specifically.
jens@13
    50
  } else if (level < Z_BEST_SPEED) {
jens@13
    51
    level = Z_BEST_SPEED;
jens@13
    52
  } else if (level > Z_BEST_COMPRESSION) {
jens@13
    53
    level = Z_BEST_COMPRESSION;
jens@13
    54
  }
jens@13
    55
jens@13
    56
  z_stream strm;
jens@13
    57
  bzero(&strm, sizeof(z_stream));
jens@13
    58
jens@13
    59
  int windowBits = 15; // the default
jens@13
    60
  int memLevel = 8; // the default
jens@13
    61
  if (useGzip) {
jens@13
    62
    windowBits += 16; // enable gzip header instead of zlib header
jens@13
    63
  }
jens@13
    64
  int retCode;
jens@13
    65
  if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits,
jens@13
    66
                              memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) {
jens@13
    67
    // COV_NF_START - no real way to force this in a unittest (we guard all args)
jens@13
    68
    _GTMDevLog(@"Failed to init for deflate w/ level %d, error %d",
jens@13
    69
               level, retCode);
jens@13
    70
    return nil;
jens@13
    71
    // COV_NF_END
jens@13
    72
  }
jens@13
    73
jens@13
    74
  // hint the size at 1/4 the input size
jens@13
    75
  NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)];
jens@13
    76
  unsigned char output[kChunkSize];
jens@13
    77
jens@13
    78
  // setup the input
jens@13
    79
  strm.avail_in = (unsigned int)length;
jens@13
    80
  strm.next_in = (unsigned char*)bytes;
jens@13
    81
jens@13
    82
  // loop to collect the data
jens@13
    83
  do {
jens@13
    84
    // update what we're passing in
jens@13
    85
    strm.avail_out = kChunkSize;
jens@13
    86
    strm.next_out = output;
jens@13
    87
    retCode = deflate(&strm, Z_FINISH);
jens@13
    88
    if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
jens@13
    89
      // COV_NF_START - no real way to force this in a unittest
jens@13
    90
      // (in inflate, we can feed bogus/truncated data to test, but an error
jens@13
    91
      // here would be some internal issue w/in zlib, and there isn't any real
jens@13
    92
      // way to test it)
jens@13
    93
      _GTMDevLog(@"Error trying to deflate some of the payload, error %d",
jens@13
    94
                 retCode);
jens@13
    95
      deflateEnd(&strm);
jens@13
    96
      return nil;
jens@13
    97
      // COV_NF_END
jens@13
    98
    }
jens@13
    99
    // collect what we got
jens@13
   100
    unsigned gotBack = kChunkSize - strm.avail_out;
jens@13
   101
    if (gotBack > 0) {
jens@13
   102
      [result appendBytes:output length:gotBack];
jens@13
   103
    }
jens@13
   104
jens@13
   105
  } while (retCode == Z_OK);
jens@13
   106
jens@13
   107
  // if the loop exits, we used all input and the stream ended
jens@13
   108
  _GTMDevAssert(strm.avail_in == 0,
jens@13
   109
                @"thought we finished deflate w/o using all input, %u bytes left",
jens@13
   110
                strm.avail_in);
jens@13
   111
  _GTMDevAssert(retCode == Z_STREAM_END,
jens@13
   112
                @"thought we finished deflate w/o getting a result of stream end, code %d",
jens@13
   113
                retCode);
jens@13
   114
jens@13
   115
  // clean up
jens@13
   116
  deflateEnd(&strm);
jens@13
   117
jens@13
   118
  return result;
jens@13
   119
} // gtm_dataByCompressingBytes:length:compressionLevel:useGzip:
jens@13
   120
  
jens@13
   121
jens@13
   122
@end
jens@13
   123
jens@13
   124
jens@13
   125
@implementation NSData (GTMZLibAdditions)
jens@13
   126
jens@13
   127
+ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
jens@13
   128
                             length:(NSUInteger)length {
jens@13
   129
  return [self gtm_dataByCompressingBytes:bytes
jens@13
   130
                                   length:length
jens@13
   131
                         compressionLevel:Z_DEFAULT_COMPRESSION
jens@13
   132
                                  useGzip:YES];
jens@13
   133
} // gtm_dataByGzippingBytes:length:
jens@13
   134
jens@13
   135
+ (NSData *)gtm_dataByGzippingData:(NSData *)data {
jens@13
   136
  return [self gtm_dataByCompressingBytes:[data bytes]
jens@13
   137
                                   length:[data length]
jens@13
   138
                         compressionLevel:Z_DEFAULT_COMPRESSION
jens@13
   139
                                  useGzip:YES];
jens@13
   140
} // gtm_dataByGzippingData:
jens@13
   141
jens@13
   142
+ (NSData *)gtm_dataByGzippingBytes:(const void *)bytes
jens@13
   143
                             length:(NSUInteger)length
jens@13
   144
                   compressionLevel:(int)level {
jens@13
   145
  return [self gtm_dataByCompressingBytes:bytes
jens@13
   146
                                   length:length
jens@13
   147
                         compressionLevel:level
jens@13
   148
                                  useGzip:YES];
jens@13
   149
} // gtm_dataByGzippingBytes:length:level:
jens@13
   150
jens@13
   151
+ (NSData *)gtm_dataByGzippingData:(NSData *)data
jens@13
   152
                  compressionLevel:(int)level {
jens@13
   153
  return [self gtm_dataByCompressingBytes:[data bytes]
jens@13
   154
                                   length:[data length]
jens@13
   155
                         compressionLevel:level
jens@13
   156
                                  useGzip:YES];
jens@13
   157
} // gtm_dataByGzippingData:level:
jens@13
   158
jens@13
   159
+ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
jens@13
   160
                              length:(NSUInteger)length {
jens@13
   161
  return [self gtm_dataByCompressingBytes:bytes
jens@13
   162
                                   length:length
jens@13
   163
                         compressionLevel:Z_DEFAULT_COMPRESSION
jens@13
   164
                                  useGzip:NO];
jens@13
   165
} // gtm_dataByDeflatingBytes:length:
jens@13
   166
jens@13
   167
+ (NSData *)gtm_dataByDeflatingData:(NSData *)data {
jens@13
   168
  return [self gtm_dataByCompressingBytes:[data bytes]
jens@13
   169
                                   length:[data length]
jens@13
   170
                         compressionLevel:Z_DEFAULT_COMPRESSION
jens@13
   171
                                  useGzip:NO];
jens@13
   172
} // gtm_dataByDeflatingData:
jens@13
   173
jens@13
   174
+ (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes
jens@13
   175
                              length:(NSUInteger)length
jens@13
   176
                    compressionLevel:(int)level {
jens@13
   177
  return [self gtm_dataByCompressingBytes:bytes
jens@13
   178
                                   length:length
jens@13
   179
                         compressionLevel:level
jens@13
   180
                                  useGzip:NO];
jens@13
   181
} // gtm_dataByDeflatingBytes:length:level:
jens@13
   182
jens@13
   183
+ (NSData *)gtm_dataByDeflatingData:(NSData *)data
jens@13
   184
                   compressionLevel:(int)level {
jens@13
   185
  return [self gtm_dataByCompressingBytes:[data bytes]
jens@13
   186
                                   length:[data length]
jens@13
   187
                         compressionLevel:level
jens@13
   188
                                  useGzip:NO];
jens@13
   189
} // gtm_dataByDeflatingData:level:
jens@13
   190
jens@13
   191
+ (NSData *)gtm_dataByInflatingBytes:(const void *)bytes
jens@13
   192
                              length:(NSUInteger)length {
jens@13
   193
  if (!bytes || !length) {
jens@13
   194
    return nil;
jens@13
   195
  }
jens@13
   196
  
jens@13
   197
  // TODO: support 64bit inputs
jens@13
   198
  // avail_in is a uInt, so if length > UINT_MAX we actually need to loop
jens@13
   199
  // feeding the data until we've gotten it all in.  not supporting this
jens@13
   200
  // at the moment.
jens@13
   201
  _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths");
jens@13
   202
jens@13
   203
  z_stream strm;
jens@13
   204
  bzero(&strm, sizeof(z_stream));
jens@13
   205
jens@13
   206
  // setup the input
jens@13
   207
  strm.avail_in = (unsigned int)length;
jens@13
   208
  strm.next_in = (unsigned char*)bytes;
jens@13
   209
jens@13
   210
  int windowBits = 15; // 15 to enable any window size
jens@13
   211
  windowBits += 32; // and +32 to enable zlib or gzip header detection.
jens@13
   212
  int retCode;
jens@13
   213
  if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) {
jens@13
   214
    // COV_NF_START - no real way to force this in a unittest (we guard all args)
jens@13
   215
    _GTMDevLog(@"Failed to init for inflate, error %d", retCode);
jens@13
   216
    return nil;
jens@13
   217
    // COV_NF_END
jens@13
   218
  }
jens@13
   219
jens@13
   220
  // hint the size at 4x the input size
jens@13
   221
  NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)];
jens@13
   222
  unsigned char output[kChunkSize];
jens@13
   223
jens@13
   224
  // loop to collect the data
jens@13
   225
  do {
jens@13
   226
    // update what we're passing in
jens@13
   227
    strm.avail_out = kChunkSize;
jens@13
   228
    strm.next_out = output;
jens@13
   229
    retCode = inflate(&strm, Z_NO_FLUSH);
jens@13
   230
    if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
jens@13
   231
      _GTMDevLog(@"Error trying to inflate some of the payload, error %d",
jens@13
   232
                 retCode);
jens@13
   233
      inflateEnd(&strm);
jens@13
   234
      return nil;
jens@13
   235
    }
jens@13
   236
    // collect what we got
jens@13
   237
    unsigned gotBack = kChunkSize - strm.avail_out;
jens@13
   238
    if (gotBack > 0) {
jens@13
   239
      [result appendBytes:output length:gotBack];
jens@13
   240
    }
jens@13
   241
jens@13
   242
  } while (retCode == Z_OK);
jens@13
   243
jens@13
   244
  // make sure there wasn't more data tacked onto the end of a valid compressed
jens@13
   245
  // stream.
jens@13
   246
  if (strm.avail_in != 0) {
jens@13
   247
    _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left",
jens@13
   248
               strm.avail_in);
jens@13
   249
    result = nil;
jens@13
   250
  }
jens@13
   251
  // the only way out of the loop was by hitting the end of the stream
jens@13
   252
  _GTMDevAssert(retCode == Z_STREAM_END,
jens@13
   253
                @"thought we finished inflate w/o getting a result of stream end, code %d",
jens@13
   254
                retCode);
jens@13
   255
jens@13
   256
  // clean up
jens@13
   257
  inflateEnd(&strm);
jens@13
   258
jens@13
   259
  return result;
jens@13
   260
} // gtm_dataByInflatingBytes:length:
jens@13
   261
jens@13
   262
+ (NSData *)gtm_dataByInflatingData:(NSData *)data {
jens@13
   263
  return [self gtm_dataByInflatingBytes:[data bytes]
jens@13
   264
                                 length:[data length]];
jens@13
   265
} // gtm_dataByInflatingData:
jens@13
   266
jens@13
   267
@end