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