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