View Javadoc
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 }