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  
46      /**
47       * Constructor with default configuration.
48       */
49      public PerMessageDeflateClientExtensionHandshaker() {
50          this(6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, false, false);
51      }
52  
53      /**
54       * Constructor with custom configuration.
55       *
56       * @param compressionLevel
57       *            Compression level between 0 and 9 (default is 6).
58       * @param allowClientWindowSize
59       *            allows WebSocket server to customize the client inflater window size
60       *            (default is false).
61       * @param requestedServerWindowSize
62       *            indicates the requested sever window size to use if server inflater is customizable.
63       * @param allowClientNoContext
64       *            allows WebSocket server to activate client_no_context_takeover
65       *            (default is false).
66       * @param requestedServerNoContext
67       *            indicates if client needs to activate server_no_context_takeover
68       *            if server is compatible with (default is false).
69       */
70      public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
71              boolean allowClientWindowSize, int requestedServerWindowSize,
72              boolean allowClientNoContext, boolean requestedServerNoContext) {
73          this(compressionLevel, allowClientWindowSize, requestedServerWindowSize,
74               allowClientNoContext, requestedServerNoContext, WebSocketExtensionFilterProvider.DEFAULT);
75      }
76  
77      /**
78       * Constructor with custom configuration.
79       *
80       * @param compressionLevel
81       *            Compression level between 0 and 9 (default is 6).
82       * @param allowClientWindowSize
83       *            allows WebSocket server to customize the client inflater window size
84       *            (default is false).
85       * @param requestedServerWindowSize
86       *            indicates the requested sever window size to use if server inflater is customizable.
87       * @param allowClientNoContext
88       *            allows WebSocket server to activate client_no_context_takeover
89       *            (default is false).
90       * @param requestedServerNoContext
91       *            indicates if client needs to activate server_no_context_takeover
92       *            if server is compatible with (default is false).
93       * @param extensionFilterProvider
94       *            provides client extension filters for per message deflate encoder and decoder.
95       */
96      public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
97              boolean allowClientWindowSize, int requestedServerWindowSize,
98              boolean allowClientNoContext, boolean requestedServerNoContext,
99              WebSocketExtensionFilterProvider extensionFilterProvider) {
100 
101         if (requestedServerWindowSize > MAX_WINDOW_SIZE || requestedServerWindowSize < MIN_WINDOW_SIZE) {
102             throw new IllegalArgumentException(
103                     "requestedServerWindowSize: " + requestedServerWindowSize + " (expected: 8-15)");
104         }
105         if (compressionLevel < 0 || compressionLevel > 9) {
106             throw new IllegalArgumentException(
107                     "compressionLevel: " + compressionLevel + " (expected: 0-9)");
108         }
109         this.compressionLevel = compressionLevel;
110         this.allowClientWindowSize = allowClientWindowSize;
111         this.requestedServerWindowSize = requestedServerWindowSize;
112         this.allowClientNoContext = allowClientNoContext;
113         this.requestedServerNoContext = requestedServerNoContext;
114         this.extensionFilterProvider = checkNotNull(extensionFilterProvider, "extensionFilterProvider");
115     }
116 
117     @Override
118     public WebSocketExtensionData newRequestData() {
119         HashMap<String, String> parameters = new HashMap<String, String>(4);
120         if (requestedServerNoContext) {
121             parameters.put(SERVER_NO_CONTEXT, null);
122         }
123         if (allowClientNoContext) {
124             parameters.put(CLIENT_NO_CONTEXT, null);
125         }
126         if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
127             parameters.put(SERVER_MAX_WINDOW, Integer.toString(requestedServerWindowSize));
128         }
129         if (allowClientWindowSize) {
130             parameters.put(CLIENT_MAX_WINDOW, null);
131         }
132         return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
133     }
134 
135     @Override
136     public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
137         if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
138             return null;
139         }
140 
141         boolean succeed = true;
142         int clientWindowSize = MAX_WINDOW_SIZE;
143         int serverWindowSize = MAX_WINDOW_SIZE;
144         boolean serverNoContext = false;
145         boolean clientNoContext = false;
146 
147         Iterator<Entry<String, String>> parametersIterator =
148                 extensionData.parameters().entrySet().iterator();
149         while (succeed && parametersIterator.hasNext()) {
150             Entry<String, String> parameter = parametersIterator.next();
151 
152             if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
153                 // allowed client_window_size_bits
154                 if (allowClientWindowSize) {
155                     clientWindowSize = Integer.parseInt(parameter.getValue());
156                     if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
157                         succeed = false;
158                     }
159                 } else {
160                     succeed = false;
161                 }
162             } else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
163                 // acknowledged server_window_size_bits
164                 serverWindowSize = Integer.parseInt(parameter.getValue());
165                 if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
166                     succeed = false;
167                 }
168             } else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
169                 // allowed client_no_context_takeover
170                 if (allowClientNoContext) {
171                     clientNoContext = true;
172                 } else {
173                     succeed = false;
174                 }
175             } else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
176                 // acknowledged server_no_context_takeover
177                 serverNoContext = true;
178             } else {
179                 // unknown parameter
180                 succeed = false;
181             }
182         }
183 
184         if ((requestedServerNoContext && !serverNoContext) ||
185                 requestedServerWindowSize < serverWindowSize) {
186             succeed = false;
187         }
188 
189         if (succeed) {
190             return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
191                     clientNoContext, clientWindowSize, extensionFilterProvider);
192         } else {
193             return null;
194         }
195     }
196 
197     private final class PermessageDeflateExtension implements WebSocketClientExtension {
198 
199         private final boolean serverNoContext;
200         private final int serverWindowSize;
201         private final boolean clientNoContext;
202         private final int clientWindowSize;
203         private final WebSocketExtensionFilterProvider extensionFilterProvider;
204 
205         @Override
206         public int rsv() {
207             return RSV1;
208         }
209 
210         PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
211                 boolean clientNoContext, int clientWindowSize,
212                 WebSocketExtensionFilterProvider extensionFilterProvider) {
213             this.serverNoContext = serverNoContext;
214             this.serverWindowSize = serverWindowSize;
215             this.clientNoContext = clientNoContext;
216             this.clientWindowSize = clientWindowSize;
217             this.extensionFilterProvider = extensionFilterProvider;
218         }
219 
220         @Override
221         public WebSocketExtensionEncoder newExtensionEncoder() {
222             return new PerMessageDeflateEncoder(compressionLevel, clientWindowSize, clientNoContext,
223                                                 extensionFilterProvider.encoderFilter());
224         }
225 
226         @Override
227         public WebSocketExtensionDecoder newExtensionDecoder() {
228             return new PerMessageDeflateDecoder(serverNoContext, extensionFilterProvider.decoderFilter());
229         }
230     }
231 
232 }