1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include <NGStreams/NGStreamExceptions.h>
23#include <NGStreams/NGGZipStream.h>
24#include <NGExtensions/NSData+gzip.h>
25#include "common.h"
26
27#ifdef Assert
28#undef Assert
29#endif
30
31#include <zlib.h>
32#ifndef DEF_MEM_LEVEL /* zutil.h */
33#  if MAX_MEM_LEVEL >= 8
34#    define DEF_MEM_LEVEL 8
35#  else
36#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
37#  endif
38#  define OS_CODE  0x07 /* TODO: probably need to adjust that ... */
39#endif
40
41#undef Assert
42
43@implementation NGGZipStream
44
45- (id)initWithSource:(id<NGStream>)_source level:(int)_level {
46  if ((self = [super initWithSource:_source])) {
47    z_stream *zout;
48
49    NSAssert1((_level >= NGGZipMinimalCompression &&
50               _level <= NGGZipMaximalCompression)
51              || (_level == Z_DEFAULT_COMPRESSION),
52              @"invalid compression level %i (0-9)", _level);
53
54    self->outBufLen = 2048;
55#if LIB_FOUNDATION_LIBRARY
56    self->outBuf    = NSZoneMallocAtomic([self zone], self->outBufLen);
57    self->outp       = NSZoneMallocAtomic([self zone], sizeof(z_stream));
58#else
59    self->outBuf    = NSZoneMalloc([self zone], self->outBufLen);
60    self->outp       = NSZoneMalloc([self zone], sizeof(z_stream));
61#endif
62    zout = self->outp;
63    zout->zalloc    = (alloc_func)NULL;
64    zout->zfree     = (free_func)NULL;
65    zout->opaque    = (voidpf)NULL;
66    zout->next_out  = self->outBuf;
67    zout->avail_out = self->outBufLen;
68    zout->next_in   = Z_NULL;
69    zout->avail_in  = 0;
70    self->crc       = crc32(0L, Z_NULL, 0);
71
72    if (deflateInit2(zout, _level, Z_DEFLATED, -MAX_WBITS,
73                     DEF_MEM_LEVEL, 0) != Z_OK) {
74      NSLog(@"Could not init deflate ..");
75      self = [self autorelease];
76      return nil;
77    }
78  }
79  return self;
80}
81
82- (void)gcFinalize {
83  [self close];
84}
85
86- (void)dealloc {
87  if (self->outBuf) NSZoneFree([self zone], self->outBuf);
88  if (self->outp)    NSZoneFree([self zone], self->outp);
89  [self gcFinalize];
90  [super dealloc];
91}
92
93/* headers */
94
95- (void)writeGZipHeader {
96  // gzip header
97  char buf[10] = {
98    0x1f, 0x8b,    // magic
99    Z_DEFLATED, 0, // flags
100    0, 0, 0, 0,    // time
101    0, OS_CODE     // flags
102  };
103
104  [self safeWriteBytes:buf count:10];
105}
106
107static inline void putLong(NGGZipStream *self, uLong x) {
108  int n;
109  for (n = 0; n < 4; n++) {
110    unsigned char c = (int)(x & 0xff);
111    [self safeWriteBytes:&c count:1];
112    x >>= 8;
113  }
114}
115- (void)writeGZipTrailer {
116  putLong(self, self->crc);
117  putLong(self, ((z_stream *)self->outp)->total_in);
118}
119
120/* primitives */
121
122- (unsigned)readBytes:(void *)_buf count:(unsigned)_len { // decoder
123  [self notImplemented:_cmd];
124  return -1;
125}
126
127- (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len { // encoder
128  z_stream *zout = self->outp;
129
130  if (!self->headerIsWritten) [self writeGZipHeader];
131
132  { // gz_write
133    zout->next_in   = (void*)_buf;
134    zout->avail_in  = _len;
135
136    while (zout->avail_in > 0) {
137      int errorCode;
138
139      if (zout->avail_out == 0) {
140        [self safeWriteBytes:self->outBuf count:self->outBufLen];
141        zout->next_out  = self->outBuf; // reset buffer position
142        zout->avail_out = self->outBufLen;
143      }
144      errorCode = deflate(self->outp, Z_NO_FLUSH);
145      if (errorCode != Z_OK) {
146        if (zout->state) deflateEnd(self->outp);
147        [NGStreamException raiseWithStream:self
148                           format:@"could not deflate chunk !"];
149      }
150    }
151    self->crc = crc32(self->crc, _buf, _len);
152  }
153  return _len;
154}
155- (BOOL)safeWriteBytes:(const void *)_buf count:(unsigned)_len { // encoder
156  // gzip writes are safe
157  if ([self writeBytes:_buf count:_len] == NGStreamError)
158    return NO;
159  else
160    return YES;
161}
162
163- (void)close {
164  [self flush];
165  [self writeGZipTrailer];
166  if (((z_stream *)self->outp)->state) deflateEnd(self->outp);
167  [super close];
168}
169
170- (void)flush {
171  int      errorCode = Z_OK;
172  z_stream *zout     = self->outp;
173  BOOL     done      = NO;
174
175  zout->next_in  = NULL;
176  zout->avail_in = 0; // should be zero already anyway
177
178  while (1) {
179    int len = self->outBufLen - zout->avail_out;
180
181    if (len > 0) {
182      [self safeWriteBytes:self->outBuf count:len];
183      zout->next_out  = self->outBuf;
184      zout->avail_out = self->outBufLen;
185    }
186    if (done)
187      break;
188    errorCode = deflate(zout, Z_FINISH);
189
190    // deflate has finished flushing only when it hasn't used up
191    // all the available space in the output buffer:
192    done = (zout->avail_out != 0 || errorCode == Z_STREAM_END);
193
194    if (errorCode != Z_OK && errorCode != Z_STREAM_END)
195      break;
196  }
197  if (errorCode != Z_STREAM_END) {
198    if (zout->state) deflateEnd(zout);
199    [NGStreamException raiseWithStream:self format:@"flush failed"];
200  }
201
202  [super flush];
203}
204
205@end
206