View Javadoc
1   /*
2    * Copyright 2014 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.http.websocketx.extensions.compression;
17  
18  import static io.netty.handler.codec.http.websocketx.extensions.compression.
19          PerMessageDeflateServerExtensionHandshaker.*;
20  
21  import io.netty.handler.codec.compression.ZlibCodecFactory;
22  import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
23  import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
24  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
25  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
26  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
27  
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Map.Entry;
31  
32  /**
33   * <a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
34   * handshake implementation.
35   */
36  public final class PerMessageDeflateClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
37  
38      private final int compressionLevel;
39      private final boolean allowClientWindowSize;
40      private final int requestedServerWindowSize;
41      private final boolean allowClientNoContext;
42      private final boolean requestedServerNoContext;
43  
44      /**
45       * Constructor with default configuration.
46       */
47      public PerMessageDeflateClientExtensionHandshaker() {
48          this(6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, false, false);
49      }
50  
51      /**
52       * Constructor with custom configuration.
53       *
54       * @param compressionLevel
55       *            Compression level between 0 and 9 (default is 6).
56       * @param allowClientWindowSize
57       *            allows WebSocket server to customize the client inflater window size
58       *            (default is false).
59       * @param requestedServerWindowSize
60       *            indicates the requested sever window size to use if server inflater is customizable.
61       * @param allowClientNoContext
62       *            allows WebSocket server to activate client_no_context_takeover
63       *            (default is false).
64       * @param requestedServerNoContext
65       *            indicates if client needs to activate server_no_context_takeover
66       *            if server is compatible with (default is false).
67       */
68      public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
69              boolean allowClientWindowSize, int requestedServerWindowSize,
70              boolean allowClientNoContext, boolean requestedServerNoContext) {
71          if (requestedServerWindowSize > MAX_WINDOW_SIZE || requestedServerWindowSize < MIN_WINDOW_SIZE) {
72              throw new IllegalArgumentException(
73                      "requestedServerWindowSize: " + requestedServerWindowSize + " (expected: 8-15)");
74          }
75          if (compressionLevel < 0 || compressionLevel > 9) {
76              throw new IllegalArgumentException(
77                      "compressionLevel: " + compressionLevel + " (expected: 0-9)");
78          }
79          this.compressionLevel = compressionLevel;
80          this.allowClientWindowSize = allowClientWindowSize;
81          this.requestedServerWindowSize = requestedServerWindowSize;
82          this.allowClientNoContext = allowClientNoContext;
83          this.requestedServerNoContext = requestedServerNoContext;
84      }
85  
86      @Override
87      public WebSocketExtensionData newRequestData() {
88          HashMap<String, String> parameters = new HashMap<String, String>(4);
89          if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
90              parameters.put(SERVER_NO_CONTEXT, null);
91          }
92          if (allowClientNoContext) {
93              parameters.put(CLIENT_NO_CONTEXT, null);
94          }
95          if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
96              parameters.put(SERVER_MAX_WINDOW, Integer.toString(requestedServerWindowSize));
97          }
98          if (allowClientWindowSize) {
99              parameters.put(CLIENT_MAX_WINDOW, null);
100         }
101         return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
102     }
103 
104     @Override
105     public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
106         if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
107             return null;
108         }
109 
110         boolean succeed = true;
111         int clientWindowSize = MAX_WINDOW_SIZE;
112         int serverWindowSize = MAX_WINDOW_SIZE;
113         boolean serverNoContext = false;
114         boolean clientNoContext = false;
115 
116         Iterator<Entry<String, String>> parametersIterator =
117                 extensionData.parameters().entrySet().iterator();
118         while (succeed && parametersIterator.hasNext()) {
119             Entry<String, String> parameter = parametersIterator.next();
120 
121             if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
122                 // allowed client_window_size_bits
123                 if (allowClientWindowSize) {
124                     clientWindowSize = Integer.parseInt(parameter.getValue());
125                 } else {
126                     succeed = false;
127                 }
128             } else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
129                 // acknowledged server_window_size_bits
130                 serverWindowSize = Integer.parseInt(parameter.getValue());
131                 if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
132                     succeed = false;
133                 }
134             } else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
135                 // allowed client_no_context_takeover
136                 if (allowClientNoContext) {
137                     clientNoContext = true;
138                 } else {
139                     succeed = false;
140                 }
141             } else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
142                 // acknowledged server_no_context_takeover
143                 if (requestedServerNoContext) {
144                     serverNoContext = true;
145                 } else {
146                     succeed = false;
147                 }
148             } else {
149                 // unknown parameter
150                 succeed = false;
151             }
152         }
153 
154         if ((requestedServerNoContext && !serverNoContext) ||
155                 requestedServerWindowSize != serverWindowSize) {
156             succeed = false;
157         }
158 
159         if (succeed) {
160             return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
161                     clientNoContext, clientWindowSize);
162         } else {
163             return null;
164         }
165     }
166 
167     private final class PermessageDeflateExtension implements WebSocketClientExtension {
168 
169         private final boolean serverNoContext;
170         private final int serverWindowSize;
171         private final boolean clientNoContext;
172         private final int clientWindowSize;
173 
174         @Override
175         public int rsv() {
176             return RSV1;
177         }
178 
179         public PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
180                 boolean clientNoContext, int clientWindowSize) {
181             this.serverNoContext = serverNoContext;
182             this.serverWindowSize = serverWindowSize;
183             this.clientNoContext = clientNoContext;
184             this.clientWindowSize = clientWindowSize;
185         }
186 
187         @Override
188         public WebSocketExtensionEncoder newExtensionEncoder() {
189             return new PerMessageDeflateEncoder(compressionLevel, serverWindowSize, serverNoContext);
190         }
191 
192         @Override
193         public WebSocketExtensionDecoder newExtensionDecoder() {
194             return new PerMessageDeflateDecoder(clientNoContext);
195         }
196     }
197 
198 }