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  *   http://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 io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufAllocator;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.handler.codec.ByteToMessageDecoder;
22
23 /**
24  * Decompresses a {@link ByteBuf} using the deflate algorithm.
25  */

26 public abstract class ZlibDecoder extends ByteToMessageDecoder {
27
28     /**
29      * Maximum allowed size of the decompression buffer.
30      */

31     protected final int maxAllocation;
32
33     /**
34      * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0.
35      */

36     public ZlibDecoder() {
37         this(0);
38     }
39
40     /**
41      * Construct a new ZlibDecoder.
42      * @param maxAllocation
43      *          Maximum size of the decompression buffer. Must be >= 0.
44      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
45      */

46     public ZlibDecoder(int maxAllocation) {
47         if (maxAllocation < 0) {
48             throw new IllegalArgumentException("maxAllocation must be >= 0");
49         }
50         this.maxAllocation = maxAllocation;
51     }
52
53     /**
54      * Returns {@code trueif and only if the end of the compressed stream
55      * has been reached.
56      */

57     public abstract boolean isClosed();
58
59     /**
60      * Allocate or expand the decompression buffer, without exceeding the maximum allocation.
61      * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the buffer is full and cannot be expanded further.
62      */

63     protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf buffer, int preferredSize) {
64         if (buffer == null) {
65             if (maxAllocation == 0) {
66                 return ctx.alloc().heapBuffer(preferredSize);
67             }
68
69             return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation);
70         }
71
72         // this always expands the buffer if possible, even if the expansion is less than preferredSize
73         // we throw the exception only if the buffer could not be expanded at all
74         // this means that one final attempt to deserialize will always be made with the buffer at maxAllocation
75         if (buffer.ensureWritable(preferredSize, true) == 1) {
76             // buffer must be consumed so subclasses don't add it to output
77             // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference
78             // but wait until after to consume it so the subclass can tell how much output is really in the buffer
79             decompressionBufferExhausted(buffer.duplicate());
80             buffer.skipBytes(buffer.readableBytes());
81             throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity());
82         }
83
84         return buffer;
85     }
86
87     /**
88      * Called when the decompression buffer cannot be expanded further.
89      * Default implementation is a no-op, but subclasses can override in case they want to
90      * do something before the {@link DecompressionException} is thrown, such as log the
91      * data that was decompressed so far.
92      */

93     protected void decompressionBufferExhausted(ByteBuf buffer) {
94     }
95
96 }
97