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