1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.compression;
17
18 import com.jcraft.jzlib.Deflater;
19 import com.jcraft.jzlib.JZlib;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.Unpooled;
22 import io.netty.channel.ChannelFuture;
23 import io.netty.channel.ChannelFutureListener;
24 import io.netty.channel.ChannelHandlerContext;
25 import io.netty.channel.ChannelPromise;
26 import io.netty.channel.ChannelPromiseNotifier;
27 import io.netty.util.concurrent.EventExecutor;
28 import io.netty.util.internal.EmptyArrays;
29
30 import java.util.concurrent.TimeUnit;
31
32 /**
33 * Compresses a {@link ByteBuf} using the deflate algorithm.
34 */
35 public class JZlibEncoder extends ZlibEncoder {
36
37 private final int wrapperOverhead;
38 private final Deflater z = new Deflater();
39 private volatile boolean finished;
40 private volatile ChannelHandlerContext ctx;
41
42 /**
43 * Creates a new zlib encoder with the default compression level ({@code 6}),
44 * default window bits ({@code 15}), default memory level ({@code 8}),
45 * and the default wrapper ({@link ZlibWrapper#ZLIB}).
46 *
47 * @throws CompressionException if failed to initialize zlib
48 */
49 public JZlibEncoder() {
50 this(6);
51 }
52
53 /**
54 * Creates a new zlib encoder with the specified {@code compressionLevel},
55 * default window bits ({@code 15}), default memory level ({@code 8}),
56 * and the default wrapper ({@link ZlibWrapper#ZLIB}).
57 *
58 * @param compressionLevel
59 * {@code 1} yields the fastest compression and {@code 9} yields the
60 * best compression. {@code 0} means no compression. The default
61 * compression level is {@code 6}.
62 *
63 * @throws CompressionException if failed to initialize zlib
64 */
65 public JZlibEncoder(int compressionLevel) {
66 this(ZlibWrapper.ZLIB, compressionLevel);
67 }
68
69 /**
70 * Creates a new zlib encoder with the default compression level ({@code 6}),
71 * default window bits ({@code 15}), default memory level ({@code 8}),
72 * and the specified wrapper.
73 *
74 * @throws CompressionException if failed to initialize zlib
75 */
76 public JZlibEncoder(ZlibWrapper wrapper) {
77 this(wrapper, 6);
78 }
79
80 /**
81 * Creates a new zlib encoder with the specified {@code compressionLevel},
82 * default window bits ({@code 15}), default memory level ({@code 8}),
83 * and the specified wrapper.
84 *
85 * @param compressionLevel
86 * {@code 1} yields the fastest compression and {@code 9} yields the
87 * best compression. {@code 0} means no compression. The default
88 * compression level is {@code 6}.
89 *
90 * @throws CompressionException if failed to initialize zlib
91 */
92 public JZlibEncoder(ZlibWrapper wrapper, int compressionLevel) {
93 this(wrapper, compressionLevel, 15, 8);
94 }
95
96 /**
97 * Creates a new zlib encoder with the specified {@code compressionLevel},
98 * the specified {@code windowBits}, the specified {@code memLevel}, and
99 * the specified wrapper.
100 *
101 * @param compressionLevel
102 * {@code 1} yields the fastest compression and {@code 9} yields the
103 * best compression. {@code 0} means no compression. The default
104 * compression level is {@code 6}.
105 * @param windowBits
106 * The base two logarithm of the size of the history buffer. The
107 * value should be in the range {@code 9} to {@code 15} inclusive.
108 * Larger values result in better compression at the expense of
109 * memory usage. The default value is {@code 15}.
110 * @param memLevel
111 * How much memory should be allocated for the internal compression
112 * state. {@code 1} uses minimum memory and {@code 9} uses maximum
113 * memory. Larger values result in better and faster compression
114 * at the expense of memory usage. The default value is {@code 8}
115 *
116 * @throws CompressionException if failed to initialize zlib
117 */
118 public JZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) {
119
120 if (compressionLevel < 0 || compressionLevel > 9) {
121 throw new IllegalArgumentException(
122 "compressionLevel: " + compressionLevel +
123 " (expected: 0-9)");
124 }
125 if (windowBits < 9 || windowBits > 15) {
126 throw new IllegalArgumentException(
127 "windowBits: " + windowBits + " (expected: 9-15)");
128 }
129 if (memLevel < 1 || memLevel > 9) {
130 throw new IllegalArgumentException(
131 "memLevel: " + memLevel + " (expected: 1-9)");
132 }
133 if (wrapper == null) {
134 throw new NullPointerException("wrapper");
135 }
136 if (wrapper == ZlibWrapper.ZLIB_OR_NONE) {
137 throw new IllegalArgumentException(
138 "wrapper '" + ZlibWrapper.ZLIB_OR_NONE + "' is not " +
139 "allowed for compression.");
140 }
141
142 int resultCode = z.init(
143 compressionLevel, windowBits, memLevel,
144 ZlibUtil.convertWrapperType(wrapper));
145 if (resultCode != JZlib.Z_OK) {
146 ZlibUtil.fail(z, "initialization failure", resultCode);
147 }
148
149 wrapperOverhead = ZlibUtil.wrapperOverhead(wrapper);
150 }
151
152 /**
153 * Creates a new zlib encoder with the default compression level ({@code 6}),
154 * default window bits ({@code 15}), default memory level ({@code 8}),
155 * and the specified preset dictionary. The wrapper is always
156 * {@link ZlibWrapper#ZLIB} because it is the only format that supports
157 * the preset dictionary.
158 *
159 * @param dictionary the preset dictionary
160 *
161 * @throws CompressionException if failed to initialize zlib
162 */
163 public JZlibEncoder(byte[] dictionary) {
164 this(6, dictionary);
165 }
166
167 /**
168 * Creates a new zlib encoder with the specified {@code compressionLevel},
169 * default window bits ({@code 15}), default memory level ({@code 8}),
170 * and the specified preset dictionary. The wrapper is always
171 * {@link ZlibWrapper#ZLIB} because it is the only format that supports
172 * the preset dictionary.
173 *
174 * @param compressionLevel
175 * {@code 1} yields the fastest compression and {@code 9} yields the
176 * best compression. {@code 0} means no compression. The default
177 * compression level is {@code 6}.
178 * @param dictionary the preset dictionary
179 *
180 * @throws CompressionException if failed to initialize zlib
181 */
182 public JZlibEncoder(int compressionLevel, byte[] dictionary) {
183 this(compressionLevel, 15, 8, dictionary);
184 }
185
186 /**
187 * Creates a new zlib encoder with the specified {@code compressionLevel},
188 * the specified {@code windowBits}, the specified {@code memLevel},
189 * and the specified preset dictionary. The wrapper is always
190 * {@link ZlibWrapper#ZLIB} because it is the only format that supports
191 * the preset dictionary.
192 *
193 * @param compressionLevel
194 * {@code 1} yields the fastest compression and {@code 9} yields the
195 * best compression. {@code 0} means no compression. The default
196 * compression level is {@code 6}.
197 * @param windowBits
198 * The base two logarithm of the size of the history buffer. The
199 * value should be in the range {@code 9} to {@code 15} inclusive.
200 * Larger values result in better compression at the expense of
201 * memory usage. The default value is {@code 15}.
202 * @param memLevel
203 * How much memory should be allocated for the internal compression
204 * state. {@code 1} uses minimum memory and {@code 9} uses maximum
205 * memory. Larger values result in better and faster compression
206 * at the expense of memory usage. The default value is {@code 8}
207 * @param dictionary the preset dictionary
208 *
209 * @throws CompressionException if failed to initialize zlib
210 */
211 public JZlibEncoder(int compressionLevel, int windowBits, int memLevel, byte[] dictionary) {
212 if (compressionLevel < 0 || compressionLevel > 9) {
213 throw new IllegalArgumentException("compressionLevel: " + compressionLevel + " (expected: 0-9)");
214 }
215 if (windowBits < 9 || windowBits > 15) {
216 throw new IllegalArgumentException(
217 "windowBits: " + windowBits + " (expected: 9-15)");
218 }
219 if (memLevel < 1 || memLevel > 9) {
220 throw new IllegalArgumentException(
221 "memLevel: " + memLevel + " (expected: 1-9)");
222 }
223 if (dictionary == null) {
224 throw new NullPointerException("dictionary");
225 }
226 int resultCode;
227 resultCode = z.deflateInit(
228 compressionLevel, windowBits, memLevel,
229 JZlib.W_ZLIB); // Default: ZLIB format
230 if (resultCode != JZlib.Z_OK) {
231 ZlibUtil.fail(z, "initialization failure", resultCode);
232 } else {
233 resultCode = z.deflateSetDictionary(dictionary, dictionary.length);
234 if (resultCode != JZlib.Z_OK) {
235 ZlibUtil.fail(z, "failed to set the dictionary", resultCode);
236 }
237 }
238
239 wrapperOverhead = ZlibUtil.wrapperOverhead(ZlibWrapper.ZLIB);
240 }
241
242 @Override
243 public ChannelFuture close() {
244 return close(ctx().channel().newPromise());
245 }
246
247 @Override
248 public ChannelFuture close(final ChannelPromise promise) {
249 ChannelHandlerContext ctx = ctx();
250 EventExecutor executor = ctx.executor();
251 if (executor.inEventLoop()) {
252 return finishEncode(ctx, promise);
253 } else {
254 final ChannelPromise p = ctx.newPromise();
255 executor.execute(new Runnable() {
256 @Override
257 public void run() {
258 ChannelFuture f = finishEncode(ctx(), p);
259 f.addListener(new ChannelPromiseNotifier(promise));
260 }
261 });
262 return p;
263 }
264 }
265
266 private ChannelHandlerContext ctx() {
267 ChannelHandlerContext ctx = this.ctx;
268 if (ctx == null) {
269 throw new IllegalStateException("not added to a pipeline");
270 }
271 return ctx;
272 }
273
274 @Override
275 public boolean isClosed() {
276 return finished;
277 }
278
279 @Override
280 protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
281 if (finished) {
282 out.writeBytes(in);
283 return;
284 }
285
286 int inputLength = in.readableBytes();
287 if (inputLength == 0) {
288 return;
289 }
290
291 try {
292 // Configure input.
293 boolean inHasArray = in.hasArray();
294 z.avail_in = inputLength;
295 if (inHasArray) {
296 z.next_in = in.array();
297 z.next_in_index = in.arrayOffset() + in.readerIndex();
298 } else {
299 byte[] array = new byte[inputLength];
300 in.getBytes(in.readerIndex(), array);
301 z.next_in = array;
302 z.next_in_index = 0;
303 }
304 int oldNextInIndex = z.next_in_index;
305
306 // Configure output.
307 int maxOutputLength = (int) Math.ceil(inputLength * 1.001) + 12 + wrapperOverhead;
308 out.ensureWritable(maxOutputLength);
309 z.avail_out = maxOutputLength;
310 z.next_out = out.array();
311 z.next_out_index = out.arrayOffset() + out.writerIndex();
312 int oldNextOutIndex = z.next_out_index;
313
314 // Note that Z_PARTIAL_FLUSH has been deprecated.
315 int resultCode;
316 try {
317 resultCode = z.deflate(JZlib.Z_SYNC_FLUSH);
318 } finally {
319 in.skipBytes(z.next_in_index - oldNextInIndex);
320 }
321
322 if (resultCode != JZlib.Z_OK) {
323 ZlibUtil.fail(z, "compression failure", resultCode);
324 }
325
326 int outputLength = z.next_out_index - oldNextOutIndex;
327 if (outputLength > 0) {
328 out.writerIndex(out.writerIndex() + outputLength);
329 }
330 } finally {
331 // Deference the external references explicitly to tell the VM that
332 // the allocated byte arrays are temporary so that the call stack
333 // can be utilized.
334 // I'm not sure if the modern VMs do this optimization though.
335 z.next_in = null;
336 z.next_out = null;
337 }
338 }
339
340 @Override
341 public void close(
342 final ChannelHandlerContext ctx,
343 final ChannelPromise promise) {
344 ChannelFuture f = finishEncode(ctx, ctx.newPromise());
345 f.addListener(new ChannelFutureListener() {
346 @Override
347 public void operationComplete(ChannelFuture f) throws Exception {
348 ctx.close(promise);
349 }
350 });
351
352 if (!f.isDone()) {
353 // Ensure the channel is closed even if the write operation completes in time.
354 ctx.executor().schedule(new Runnable() {
355 @Override
356 public void run() {
357 ctx.close(promise);
358 }
359 }, 10, TimeUnit.SECONDS); // FIXME: Magic number
360 }
361 }
362
363 private ChannelFuture finishEncode(ChannelHandlerContext ctx, ChannelPromise promise) {
364 if (finished) {
365 promise.setSuccess();
366 return promise;
367 }
368 finished = true;
369
370 ByteBuf footer;
371 try {
372 // Configure input.
373 z.next_in = EmptyArrays.EMPTY_BYTES;
374 z.next_in_index = 0;
375 z.avail_in = 0;
376
377 // Configure output.
378 byte[] out = new byte[32]; // room for ADLER32 + ZLIB / CRC32 + GZIP header
379 z.next_out = out;
380 z.next_out_index = 0;
381 z.avail_out = out.length;
382
383 // Write the ADLER32 checksum (stream footer).
384 int resultCode = z.deflate(JZlib.Z_FINISH);
385 if (resultCode != JZlib.Z_OK && resultCode != JZlib.Z_STREAM_END) {
386 promise.setFailure(ZlibUtil.deflaterException(z, "compression failure", resultCode));
387 return promise;
388 } else if (z.next_out_index != 0) {
389 footer = Unpooled.wrappedBuffer(out, 0, z.next_out_index);
390 } else {
391 footer = Unpooled.EMPTY_BUFFER;
392 }
393 } finally {
394 z.deflateEnd();
395
396 // Deference the external references explicitly to tell the VM that
397 // the allocated byte arrays are temporary so that the call stack
398 // can be utilized.
399 // I'm not sure if the modern VMs do this optimization though.
400 z.next_in = null;
401 z.next_out = null;
402 }
403 return ctx.writeAndFlush(footer, promise);
404 }
405
406 @Override
407 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
408 this.ctx = ctx;
409 }
410 }