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