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