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