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    *   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.http.websocketx.extensions.compression;
17  
18  import io.netty.handler.codec.compression.ZlibCodecFactory;
19  import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
20  import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
21  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
22  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
23  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
24  import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionFilterProvider;
25  
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.Map.Entry;
29  
30  import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.*;
31  import static io.netty.util.internal.ObjectUtil.*;
32  
33  /**
34   * <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
35   * handshake implementation.
36   */
37  public final class PerMessageDeflateClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
38  
39      private final int compressionLevel;
40      private final boolean allowClientWindowSize;
41      private final int requestedServerWindowSize;
42      private final boolean allowClientNoContext;
43      private final boolean requestedServerNoContext;
44      private final WebSocketExtensionFilterProvider extensionFilterProvider;
45      private final int maxAllocation;
46  
47      /**
48       * Constructor with default configuration.
49       * @deprecated
50       *            Use {@link PerMessageDeflateClientExtensionHandshaker#
51       *            PerMessageDeflateClientExtensionHandshaker(int)}.
52       */
53      @Deprecated
54      public PerMessageDeflateClientExtensionHandshaker() {
55          this(0);
56      }
57  
58      /**
59       * Constructor with default configuration.
60       * @param maxAllocation
61       *            Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
62       */
63      public PerMessageDeflateClientExtensionHandshaker(int maxAllocation) {
64          this(6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, false, false, maxAllocation);
65      }
66  
67      /**
68       * Constructor with custom configuration.
69       *
70       * @param compressionLevel
71       *            Compression level between 0 and 9 (default is 6).
72       * @param allowClientWindowSize
73       *            allows WebSocket server to customize the client inflater window size
74       *            (default is false).
75       * @param requestedServerWindowSize
76       *            indicates the requested sever window size to use if server inflater is customizable.
77       * @param allowClientNoContext
78       *            allows WebSocket server to activate client_no_context_takeover
79       *            (default is false).
80       * @param requestedServerNoContext
81       *            indicates if client needs to activate server_no_context_takeover
82       *            if server is compatible with (default is false).
83       * @deprecated
84       *            Use {@link PerMessageDeflateClientExtensionHandshaker#PerMessageDeflateClientExtensionHandshaker(
85       *            int, boolean, int, boolean, boolean, int)}.
86       */
87      @Deprecated
88      public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
89                                                        boolean allowClientWindowSize, int requestedServerWindowSize,
90                                                        boolean allowClientNoContext, boolean requestedServerNoContext) {
91          this(compressionLevel, allowClientWindowSize, requestedServerWindowSize, allowClientNoContext,
92               requestedServerNoContext, 0);
93      }
94  
95      /**
96       * Constructor with custom configuration.
97       *
98       * @param compressionLevel
99       *            Compression level between 0 and 9 (default is 6).
100      * @param allowClientWindowSize
101      *            allows WebSocket server to customize the client inflater window size
102      *            (default is false).
103      * @param requestedServerWindowSize
104      *            indicates the requested sever window size to use if server inflater is customizable.
105      * @param allowClientNoContext
106      *            allows WebSocket server to activate client_no_context_takeover
107      *            (default is false).
108      * @param requestedServerNoContext
109      *            indicates if client needs to activate server_no_context_takeover
110      *            if server is compatible with (default is false).
111      * @param maxAllocation
112      *            Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
113      */
114     public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
115             boolean allowClientWindowSize, int requestedServerWindowSize,
116             boolean allowClientNoContext, boolean requestedServerNoContext,
117             int maxAllocation) {
118         this(compressionLevel, allowClientWindowSize, requestedServerWindowSize,
119              allowClientNoContext, requestedServerNoContext, WebSocketExtensionFilterProvider.DEFAULT, maxAllocation);
120     }
121 
122     /**
123      * Constructor with custom configuration.
124      *
125      * @param compressionLevel
126      *            Compression level between 0 and 9 (default is 6).
127      * @param allowClientWindowSize
128      *            allows WebSocket server to customize the client inflater window size
129      *            (default is false).
130      * @param requestedServerWindowSize
131      *            indicates the requested sever window size to use if server inflater is customizable.
132      * @param allowClientNoContext
133      *            allows WebSocket server to activate client_no_context_takeover
134      *            (default is false).
135      * @param requestedServerNoContext
136      *            indicates if client needs to activate server_no_context_takeover
137      *            if server is compatible with (default is false).
138      * @param extensionFilterProvider
139      *            provides client extension filters for per message deflate encoder and decoder.
140      * @deprecated
141      *            Use {@link PerMessageDeflateClientExtensionHandshaker#PerMessageDeflateClientExtensionHandshaker(
142      *            int, boolean, int, boolean, boolean, WebSocketExtensionFilterProvider, int)}.
143      */
144     @Deprecated
145     public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
146                                                       boolean allowClientWindowSize, int requestedServerWindowSize,
147                                                       boolean allowClientNoContext, boolean requestedServerNoContext,
148                                                       WebSocketExtensionFilterProvider extensionFilterProvider) {
149         this(compressionLevel, allowClientWindowSize, requestedServerWindowSize,
150                 allowClientNoContext, requestedServerNoContext, extensionFilterProvider, 0);
151     }
152 
153     /**
154      * Constructor with custom configuration.
155      *
156      * @param compressionLevel
157      *            Compression level between 0 and 9 (default is 6).
158      * @param allowClientWindowSize
159      *            allows WebSocket server to customize the client inflater window size
160      *            (default is false).
161      * @param requestedServerWindowSize
162      *            indicates the requested sever window size to use if server inflater is customizable.
163      * @param allowClientNoContext
164      *            allows WebSocket server to activate client_no_context_takeover
165      *            (default is false).
166      * @param requestedServerNoContext
167      *            indicates if client needs to activate server_no_context_takeover
168      *            if server is compatible with (default is false).
169      * @param extensionFilterProvider
170      *            provides client extension filters for per message deflate encoder and decoder.
171      * @param maxAllocation
172      *            Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
173      */
174     public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
175             boolean allowClientWindowSize, int requestedServerWindowSize,
176             boolean allowClientNoContext, boolean requestedServerNoContext,
177             WebSocketExtensionFilterProvider extensionFilterProvider,
178             int maxAllocation) {
179 
180         if (requestedServerWindowSize > MAX_WINDOW_SIZE || requestedServerWindowSize < MIN_WINDOW_SIZE) {
181             throw new IllegalArgumentException(
182                     "requestedServerWindowSize: " + requestedServerWindowSize + " (expected: 8-15)");
183         }
184         if (compressionLevel < 0 || compressionLevel > 9) {
185             throw new IllegalArgumentException(
186                     "compressionLevel: " + compressionLevel + " (expected: 0-9)");
187         }
188         this.compressionLevel = compressionLevel;
189         this.allowClientWindowSize = allowClientWindowSize;
190         this.requestedServerWindowSize = requestedServerWindowSize;
191         this.allowClientNoContext = allowClientNoContext;
192         this.requestedServerNoContext = requestedServerNoContext;
193         this.extensionFilterProvider = checkNotNull(extensionFilterProvider, "extensionFilterProvider");
194         this.maxAllocation = checkPositiveOrZero(maxAllocation, "maxAllocation");
195     }
196 
197     @Override
198     public WebSocketExtensionData newRequestData() {
199         HashMap<String, String> parameters = new HashMap<String, String>(4);
200         if (requestedServerNoContext) {
201             parameters.put(SERVER_NO_CONTEXT, null);
202         }
203         if (allowClientNoContext) {
204             parameters.put(CLIENT_NO_CONTEXT, null);
205         }
206         if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
207             parameters.put(SERVER_MAX_WINDOW, Integer.toString(requestedServerWindowSize));
208         }
209         if (allowClientWindowSize) {
210             parameters.put(CLIENT_MAX_WINDOW, null);
211         }
212         return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
213     }
214 
215     @Override
216     public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
217         if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
218             return null;
219         }
220 
221         boolean succeed = true;
222         int clientWindowSize = MAX_WINDOW_SIZE;
223         int serverWindowSize = MAX_WINDOW_SIZE;
224         boolean serverNoContext = false;
225         boolean clientNoContext = false;
226 
227         Iterator<Entry<String, String>> parametersIterator =
228                 extensionData.parameters().entrySet().iterator();
229         while (succeed && parametersIterator.hasNext()) {
230             Entry<String, String> parameter = parametersIterator.next();
231 
232             if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
233                 // allowed client_window_size_bits
234                 if (allowClientWindowSize) {
235                     clientWindowSize = Integer.parseInt(parameter.getValue());
236                     if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
237                         succeed = false;
238                     }
239                 } else {
240                     succeed = false;
241                 }
242             } else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
243                 // acknowledged server_window_size_bits
244                 serverWindowSize = Integer.parseInt(parameter.getValue());
245                 if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
246                     succeed = false;
247                 }
248             } else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
249                 // allowed client_no_context_takeover
250                 if (allowClientNoContext) {
251                     clientNoContext = true;
252                 } else {
253                     succeed = false;
254                 }
255             } else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
256                 // acknowledged server_no_context_takeover
257                 serverNoContext = true;
258             } else {
259                 // unknown parameter
260                 succeed = false;
261             }
262         }
263 
264         if ((requestedServerNoContext && !serverNoContext) ||
265                 requestedServerWindowSize < serverWindowSize) {
266             succeed = false;
267         }
268 
269         if (succeed) {
270             return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
271                     clientNoContext, clientWindowSize, extensionFilterProvider, maxAllocation);
272         } else {
273             return null;
274         }
275     }
276 
277     private final class PermessageDeflateExtension implements WebSocketClientExtension {
278 
279         private final boolean serverNoContext;
280         private final int serverWindowSize;
281         private final boolean clientNoContext;
282         private final int clientWindowSize;
283         private final WebSocketExtensionFilterProvider extensionFilterProvider;
284         private final int maxAllocation;
285 
286         @Override
287         public int rsv() {
288             return RSV1;
289         }
290 
291         PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
292                 boolean clientNoContext, int clientWindowSize,
293                 WebSocketExtensionFilterProvider extensionFilterProvider, int maxAllocation) {
294             this.serverNoContext = serverNoContext;
295             this.serverWindowSize = serverWindowSize;
296             this.clientNoContext = clientNoContext;
297             this.clientWindowSize = clientWindowSize;
298             this.extensionFilterProvider = extensionFilterProvider;
299             this.maxAllocation = maxAllocation;
300         }
301 
302         @Override
303         public WebSocketExtensionEncoder newExtensionEncoder() {
304             return new PerMessageDeflateEncoder(compressionLevel, clientWindowSize, clientNoContext,
305                                                 extensionFilterProvider.encoderFilter());
306         }
307 
308         @Override
309         public WebSocketExtensionDecoder newExtensionDecoder() {
310             return new PerMessageDeflateDecoder(serverNoContext, extensionFilterProvider.decoderFilter(),
311                                                 maxAllocation);
312         }
313     }
314 
315 }