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