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