View Javadoc
1   /*
2    * Copyright 2013 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.spdy;
17  
18  import com.jcraft.jzlib.Deflater;
19  import com.jcraft.jzlib.JZlib;
20  import io.netty.buffer.ByteBuf;
21  import io.netty.buffer.ByteBufAllocator;
22  import io.netty.buffer.Unpooled;
23  import io.netty.handler.codec.compression.CompressionException;
24  
25  import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
26  
27  class SpdyHeaderBlockJZlibEncoder extends SpdyHeaderBlockRawEncoder {
28  
29      private final Deflater z = new Deflater();
30  
31      private boolean finished;
32  
33      SpdyHeaderBlockJZlibEncoder(
34              SpdyVersion version, int compressionLevel, int windowBits, int memLevel) {
35          super(version);
36          if (compressionLevel < 0 || compressionLevel > 9) {
37              throw new IllegalArgumentException(
38                      "compressionLevel: " + compressionLevel + " (expected: 0-9)");
39          }
40          if (windowBits < 9 || windowBits > 15) {
41              throw new IllegalArgumentException(
42                      "windowBits: " + windowBits + " (expected: 9-15)");
43          }
44          if (memLevel < 1 || memLevel > 9) {
45              throw new IllegalArgumentException(
46                      "memLevel: " + memLevel + " (expected: 1-9)");
47          }
48  
49          int resultCode = z.deflateInit(
50                  compressionLevel, windowBits, memLevel, JZlib.W_ZLIB);
51          if (resultCode != JZlib.Z_OK) {
52              throw new CompressionException(
53                      "failed to initialize an SPDY header block deflater: " + resultCode);
54          } else {
55              resultCode = z.deflateSetDictionary(SPDY_DICT, SPDY_DICT.length);
56              if (resultCode != JZlib.Z_OK) {
57                  throw new CompressionException(
58                          "failed to set the SPDY dictionary: " + resultCode);
59              }
60          }
61      }
62  
63      private void setInput(ByteBuf decompressed) {
64          int len = decompressed.readableBytes();
65  
66          byte[] in;
67          int offset;
68          if (decompressed.hasArray()) {
69              in = decompressed.array();
70              offset = decompressed.arrayOffset() + decompressed.readerIndex();
71          } else {
72              in = new byte[len];
73              decompressed.getBytes(decompressed.readerIndex(), in);
74              offset = 0;
75          }
76          z.next_in = in;
77          z.next_in_index = offset;
78          z.avail_in = len;
79      }
80  
81      private ByteBuf encode(ByteBufAllocator alloc) {
82          boolean release = true;
83          ByteBuf out = null;
84          try {
85              int oldNextInIndex = z.next_in_index;
86              int oldNextOutIndex = z.next_out_index;
87  
88              int maxOutputLength = (int) Math.ceil(z.next_in.length * 1.001) + 12;
89              out = alloc.heapBuffer(maxOutputLength);
90              z.next_out = out.array();
91              z.next_out_index = out.arrayOffset() + out.writerIndex();
92              z.avail_out = maxOutputLength;
93  
94              int resultCode;
95              try {
96                  resultCode = z.deflate(JZlib.Z_SYNC_FLUSH);
97              } finally {
98                  out.skipBytes(z.next_in_index - oldNextInIndex);
99              }
100             if (resultCode != JZlib.Z_OK) {
101                 throw new CompressionException("compression failure: " + resultCode);
102             }
103 
104             int outputLength = z.next_out_index - oldNextOutIndex;
105             if (outputLength > 0) {
106                 out.writerIndex(out.writerIndex() + outputLength);
107             }
108             release = false;
109             return out;
110         } finally {
111             // Deference the external references explicitly to tell the VM that
112             // the allocated byte arrays are temporary so that the call stack
113             // can be utilized.
114             // I'm not sure if the modern VMs do this optimization though.
115             z.next_in = null;
116             z.next_out = null;
117             if (release && out != null) {
118                 out.release();
119             }
120         }
121     }
122 
123     @Override
124     public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
125         if (frame == null) {
126             throw new IllegalArgumentException("frame");
127         }
128 
129         if (finished) {
130             return Unpooled.EMPTY_BUFFER;
131         }
132 
133         ByteBuf decompressed = super.encode(alloc, frame);
134         try {
135             if (!decompressed.isReadable()) {
136                 return Unpooled.EMPTY_BUFFER;
137             }
138 
139             setInput(decompressed);
140             return encode(alloc);
141         } finally {
142             decompressed.release();
143         }
144     }
145 
146     @Override
147     public void end() {
148         if (finished) {
149             return;
150         }
151         finished = true;
152         z.deflateEnd();
153         z.next_in = null;
154         z.next_out = null;
155     }
156 }