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 * https://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.Inflater;
19 import com.jcraft.jzlib.JZlib;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.ByteBufAllocator;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.util.internal.ObjectUtil;
24
25 import java.util.List;
26
27 public class JZlibDecoder extends ZlibDecoder {
28
29 private final Inflater z = new Inflater();
30 private byte[] dictionary;
31 private static final int DEFAULT_MAX_FORWARD_BYTES = CompressionUtil.DEFAULT_MAX_FORWARD_BYTES;
32 private final int maxForwardBytes;
33 private boolean needsRead;
34 private volatile boolean finished;
35
36 /**
37 * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
38 *
39 * @throws DecompressionException if failed to initialize zlib
40 * @deprecated Use {@link JZlibDecoder#JZlibDecoder(int)}.
41 */
42 @Deprecated
43 public JZlibDecoder() {
44 this(ZlibWrapper.ZLIB, 0);
45 }
46
47 /**
48 * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
49 * and specified maximum buffer allocation.
50 *
51 * @param maxAllocation
52 * Maximum size of the decompression buffer. Must be >= 0.
53 * If zero, maximum size is decided by the {@link ByteBufAllocator}.
54 *
55 * @throws DecompressionException if failed to initialize zlib
56 */
57 public JZlibDecoder(int maxAllocation) {
58 this(ZlibWrapper.ZLIB, maxAllocation);
59 }
60
61 /**
62 * Creates a new instance with the specified wrapper.
63 *
64 * @throws DecompressionException if failed to initialize zlib
65 * @deprecated Use {@link JZlibDecoder#JZlibDecoder(ZlibWrapper, int)}.
66 */
67 @Deprecated
68 public JZlibDecoder(ZlibWrapper wrapper) {
69 this(wrapper, 0);
70 }
71
72 /**
73 * Creates a new instance with the specified wrapper and maximum buffer allocation.
74 *
75 * @param maxAllocation
76 * Maximum size of the decompression buffer. Must be >= 0.
77 * If zero, maximum size is decided by the {@link ByteBufAllocator}.
78 *
79 * @throws DecompressionException if failed to initialize zlib
80 */
81 public JZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
82 super(maxAllocation);
83 this.maxForwardBytes = maxAllocation > 0 ? maxAllocation : DEFAULT_MAX_FORWARD_BYTES;
84
85 ObjectUtil.checkNotNull(wrapper, "wrapper");
86
87 int resultCode = z.init(ZlibUtil.convertWrapperType(wrapper));
88 if (resultCode != JZlib.Z_OK) {
89 ZlibUtil.fail(z, "initialization failure", resultCode);
90 }
91 }
92
93 /**
94 * Creates a new instance with the specified preset dictionary. The wrapper
95 * is always {@link ZlibWrapper#ZLIB} because it is the only format that
96 * supports the preset dictionary.
97 *
98 * @throws DecompressionException if failed to initialize zlib
99 * @deprecated Use {@link JZlibDecoder#JZlibDecoder(byte[], int)}.
100 */
101 @Deprecated
102 public JZlibDecoder(byte[] dictionary) {
103 this(dictionary, 0);
104 }
105
106 /**
107 * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
108 * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
109 * supports the preset dictionary.
110 *
111 * @param maxAllocation
112 * Maximum size of the decompression buffer. Must be >= 0.
113 * If zero, maximum size is decided by the {@link ByteBufAllocator}.
114 *
115 * @throws DecompressionException if failed to initialize zlib
116 */
117 public JZlibDecoder(byte[] dictionary, int maxAllocation) {
118 super(maxAllocation);
119 this.maxForwardBytes = maxAllocation > 0 ? maxAllocation : DEFAULT_MAX_FORWARD_BYTES;
120 this.dictionary = ObjectUtil.checkNotNull(dictionary, "dictionary");
121 int resultCode;
122 resultCode = z.inflateInit(JZlib.W_ZLIB);
123 if (resultCode != JZlib.Z_OK) {
124 ZlibUtil.fail(z, "initialization failure", resultCode);
125 }
126 }
127
128 /**
129 * Returns {@code true} if and only if the end of the compressed stream
130 * has been reached.
131 */
132 @Override
133 public boolean isClosed() {
134 return finished;
135 }
136
137 @Override
138 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
139 needsRead = true;
140 if (finished) {
141 // Skip data received after finished.
142 in.skipBytes(in.readableBytes());
143 return;
144 }
145
146 final int inputLength = in.readableBytes();
147 if (inputLength == 0) {
148 return;
149 }
150
151 try {
152 // Configure input.
153 z.avail_in = inputLength;
154 if (in.hasArray()) {
155 z.next_in = in.array();
156 z.next_in_index = in.arrayOffset() + in.readerIndex();
157 } else {
158 byte[] array = new byte[inputLength];
159 in.getBytes(in.readerIndex(), array);
160 z.next_in = array;
161 z.next_in_index = 0;
162 }
163 final int oldNextInIndex = z.next_in_index;
164
165 // Configure output.
166 ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inputLength << 1);
167
168 try {
169 loop: for (;;) {
170 decompressed = prepareDecompressBuffer(ctx, decompressed, z.avail_in << 1);
171 z.avail_out = decompressed.writableBytes();
172 z.next_out = decompressed.array();
173 z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex();
174 int oldNextOutIndex = z.next_out_index;
175
176 // Decompress 'in' into 'out'
177 int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
178 int outputLength = z.next_out_index - oldNextOutIndex;
179 if (outputLength > 0) {
180 decompressed.writerIndex(decompressed.writerIndex() + outputLength);
181 if (maxAllocation == 0 && decompressed.readableBytes() >= maxForwardBytes) {
182 // If we don't limit the maximum allocations we should just
183 // forward the buffer directly.
184 ByteBuf buffer = decompressed;
185 decompressed = null;
186 needsRead = false;
187 ctx.fireChannelRead(buffer);
188 }
189 }
190
191 switch (resultCode) {
192 case JZlib.Z_NEED_DICT:
193 if (dictionary == null) {
194 ZlibUtil.fail(z, "decompression failure", resultCode);
195 } else {
196 resultCode = z.inflateSetDictionary(dictionary, dictionary.length);
197 if (resultCode != JZlib.Z_OK) {
198 ZlibUtil.fail(z, "failed to set the dictionary", resultCode);
199 }
200 }
201 break;
202 case JZlib.Z_STREAM_END:
203 finished = true; // Do not decode anymore.
204 z.inflateEnd();
205 break loop;
206 case JZlib.Z_OK:
207 break;
208 case JZlib.Z_BUF_ERROR:
209 if (z.avail_in <= 0) {
210 break loop;
211 }
212 break;
213 default:
214 ZlibUtil.fail(z, "decompression failure", resultCode);
215 }
216 }
217 } finally {
218 in.skipBytes(z.next_in_index - oldNextInIndex);
219 if (decompressed != null) {
220 if (decompressed.isReadable()) {
221 needsRead = false;
222 ctx.fireChannelRead(decompressed);
223 } else {
224 decompressed.release();
225 }
226 }
227 }
228 } finally {
229 // Deference the external references explicitly to tell the VM that
230 // the allocated byte arrays are temporary so that the call stack
231 // can be utilized.
232 // I'm not sure if the modern VMs do this optimization though.
233 z.next_in = null;
234 z.next_out = null;
235 }
236 }
237
238 @Override
239 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
240 // Discard bytes of the cumulation buffer if needed.
241 discardSomeReadBytes();
242
243 if (needsRead && !ctx.channel().config().isAutoRead()) {
244 ctx.read();
245 }
246 ctx.fireChannelReadComplete();
247 }
248
249 @Override
250 protected void decompressionBufferExhausted(ByteBuf buffer) {
251 finished = true;
252 }
253 }