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