blob: dd3c2367b2e229abe5154c46d143f87ab9c90b60 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
DRCf4a341b2011-08-09 11:12:55 +00002 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
Pierre Ossman5ad4d062014-07-07 14:13:46 +020020#include <stdio.h>
21
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000022#include <rdr/ZlibOutStream.h>
23#include <rdr/Exception.h>
Pierre Ossmane9e7da92016-04-20 09:38:06 +020024#include <rfb/LogWriter.h>
Adam Tkac20e0d712008-11-14 14:48:21 +000025
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000026#include <zlib.h>
27
Pierre Ossmanb5822f32011-10-18 14:27:07 +000028#undef ZLIBOUT_DEBUG
29
Pierre Ossmane9e7da92016-04-20 09:38:06 +020030static rfb::LogWriter vlog("ZlibOutStream");
31
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000032using namespace rdr;
33
34enum { DEFAULT_BUF_SIZE = 16384 };
35
36ZlibOutStream::ZlibOutStream(OutStream* os, int bufSize_, int compressLevel)
37 : underlying(os), compressionLevel(compressLevel), newLevel(compressLevel),
Pierre Ossmanb5822f32011-10-18 14:27:07 +000038 bufSize(bufSize_ ? bufSize_ : DEFAULT_BUF_SIZE), offset(0)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000039{
40 zs = new z_stream;
41 zs->zalloc = Z_NULL;
42 zs->zfree = Z_NULL;
43 zs->opaque = Z_NULL;
Pierre Ossmanb5822f32011-10-18 14:27:07 +000044 zs->next_in = Z_NULL;
45 zs->avail_in = 0;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000046 if (deflateInit(zs, compressLevel) != Z_OK) {
47 delete zs;
48 throw Exception("ZlibOutStream: deflateInit failed");
49 }
50 ptr = start = new U8[bufSize];
51 end = start + bufSize;
52}
53
54ZlibOutStream::~ZlibOutStream()
55{
56 try {
57 flush();
58 } catch (Exception&) {
59 }
60 delete [] start;
61 deflateEnd(zs);
62 delete zs;
63}
64
65void ZlibOutStream::setUnderlying(OutStream* os)
66{
67 underlying = os;
68}
69
70void ZlibOutStream::setCompressionLevel(int level)
71{
72 if (level < -1 || level > 9)
73 level = -1; // Z_DEFAULT_COMPRESSION
74
75 newLevel = level;
76}
77
78int ZlibOutStream::length()
79{
80 return offset + ptr - start;
81}
82
83void ZlibOutStream::flush()
84{
Pierre Ossmanb5822f32011-10-18 14:27:07 +000085 checkCompressionLevel();
86
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000087 zs->next_in = start;
88 zs->avail_in = ptr - start;
89
Pierre Ossmanb5822f32011-10-18 14:27:07 +000090#ifdef ZLIBOUT_DEBUG
Pierre Ossmane9e7da92016-04-20 09:38:06 +020091 vlog.debug("flush: avail_in %d",zs->avail_in);
Pierre Ossmanb5822f32011-10-18 14:27:07 +000092#endif
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000093
Pierre Ossmanb5822f32011-10-18 14:27:07 +000094 // Force out everything from the zlib encoder
95 deflate(Z_SYNC_FLUSH);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000096
97 offset += ptr - start;
98 ptr = start;
99}
100
101int ZlibOutStream::overrun(int itemSize, int nItems)
102{
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000103#ifdef ZLIBOUT_DEBUG
Pierre Ossmane9e7da92016-04-20 09:38:06 +0200104 vlog.debug("overrun");
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000105#endif
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000106
107 if (itemSize > bufSize)
108 throw Exception("ZlibOutStream overrun: max itemSize exceeded");
109
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000110 checkCompressionLevel();
DRCff5ca2d2011-08-09 20:19:59 +0000111
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000112 while (end - ptr < itemSize) {
113 zs->next_in = start;
114 zs->avail_in = ptr - start;
115
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000116 deflate(Z_NO_FLUSH);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000117
118 // output buffer not full
119
120 if (zs->avail_in == 0) {
121 offset += ptr - start;
122 ptr = start;
123 } else {
124 // but didn't consume all the data? try shifting what's left to the
125 // start of the buffer.
Pierre Ossmane9e7da92016-04-20 09:38:06 +0200126 vlog.info("z out buf not full, but in data not consumed");
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000127 memmove(start, zs->next_in, ptr - zs->next_in);
128 offset += zs->next_in - start;
129 ptr -= zs->next_in - start;
130 }
131 }
132
133 if (itemSize * nItems > end - ptr)
134 nItems = (end - ptr) / itemSize;
135
136 return nItems;
137}
138
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000139void ZlibOutStream::deflate(int flush)
140{
141 int rc;
142
143 if (!underlying)
144 throw Exception("ZlibOutStream: underlying OutStream has not been set");
145
146 if ((flush == Z_NO_FLUSH) && (zs->avail_in == 0))
147 return;
148
149 do {
150 underlying->check(1);
151 zs->next_out = underlying->getptr();
152 zs->avail_out = underlying->getend() - underlying->getptr();
153
154#ifdef ZLIBOUT_DEBUG
Pierre Ossmane9e7da92016-04-20 09:38:06 +0200155 vlog.debug("calling deflate, avail_in %d, avail_out %d",
156 zs->avail_in,zs->avail_out);
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000157#endif
158
159 rc = ::deflate(zs, flush);
160 if (rc != Z_OK) {
161 // Silly zlib returns an error if you try to flush something twice
162 if ((rc == Z_BUF_ERROR) && (flush != Z_NO_FLUSH))
163 break;
164
165 throw Exception("ZlibOutStream: deflate failed");
166 }
167
168#ifdef ZLIBOUT_DEBUG
Pierre Ossmane9e7da92016-04-20 09:38:06 +0200169 vlog.debug("after deflate: %d bytes",
170 zs->next_out-underlying->getptr());
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000171#endif
172
173 underlying->setptr(zs->next_out);
174 } while (zs->avail_out == 0);
175}
176
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000177void ZlibOutStream::checkCompressionLevel()
178{
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000179 int rc;
180
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000181 if (newLevel != compressionLevel) {
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000182#ifdef ZLIBOUT_DEBUG
Pierre Ossmane9e7da92016-04-20 09:38:06 +0200183 vlog.debug("change: avail_in %d",zs->avail_in);
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000184#endif
DRCff5ca2d2011-08-09 20:19:59 +0000185
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000186 // zlib is just horribly stupid. It does an implicit flush on
187 // parameter changes, but the flush it does is not one that forces
188 // out all the data. And since you cannot flush things again, we
189 // cannot force out our data after the parameter change. Hence we
190 // need to do a more proper flush here first.
191 deflate(Z_SYNC_FLUSH);
DRCff5ca2d2011-08-09 20:19:59 +0000192
Pierre Ossmanb5822f32011-10-18 14:27:07 +0000193 rc = deflateParams (zs, newLevel, Z_DEFAULT_STRATEGY);
194 if (rc != Z_OK) {
195 // The implicit flush can result in this error, caused by the
196 // explicit flush we did above. It should be safe to ignore though
197 // as the first flush should have left things in a stable state...
198 if (rc != Z_BUF_ERROR)
199 throw Exception("ZlibOutStream: deflateParams failed");
DRCff5ca2d2011-08-09 20:19:59 +0000200 }
201
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000202 compressionLevel = newLevel;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000203 }
204}