1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.websocket.common;
20
21 import java.nio.ByteBuffer;
22 import java.util.List;
23
24 import org.eclipse.jetty.io.ByteBufferPool;
25 import org.eclipse.jetty.util.BufferUtil;
26 import org.eclipse.jetty.websocket.api.ProtocolException;
27 import org.eclipse.jetty.websocket.api.WebSocketBehavior;
28 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
29 import org.eclipse.jetty.websocket.api.extensions.Extension;
30 import org.eclipse.jetty.websocket.api.extensions.Frame;
31
32 /**
33 * Generating a frame in WebSocket land.
34 *
35 * <pre>
36 * 0 1 2 3
37 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
38 * +-+-+-+-+-------+-+-------------+-------------------------------+
39 * |F|R|R|R| opcode|M| Payload len | Extended payload length |
40 * |I|S|S|S| (4) |A| (7) | (16/64) |
41 * |N|V|V|V| |S| | (if payload len==126/127) |
42 * | |1|2|3| |K| | |
43 * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
44 * | Extended payload length continued, if payload len == 127 |
45 * + - - - - - - - - - - - - - - - +-------------------------------+
46 * | |Masking-key, if MASK set to 1 |
47 * +-------------------------------+-------------------------------+
48 * | Masking-key (continued) | Payload Data |
49 * +-------------------------------- - - - - - - - - - - - - - - - +
50 * : Payload Data continued ... :
51 * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
52 * | Payload Data continued ... |
53 * +---------------------------------------------------------------+
54 * </pre>
55 */
56 public class Generator
57 {
58 /**
59 * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
60 */
61 public static final int MAX_HEADER_LENGTH = 28;
62
63 private final WebSocketBehavior behavior;
64 private final ByteBufferPool bufferPool;
65 private final boolean validating;
66 private final boolean readOnly;
67
68 /**
69 * Are any flags in use
70 * <p>
71 *
72 * <pre>
73 * 0100_0000 (0x40) = rsv1
74 * 0010_0000 (0x20) = rsv2
75 * 0001_0000 (0x10) = rsv3
76 * </pre>
77 */
78 private byte flagsInUse = 0x00;
79
80 /**
81 * Construct Generator with provided policy and bufferPool
82 *
83 * @param policy
84 * the policy to use
85 * @param bufferPool
86 * the buffer pool to use
87 */
88 public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool)
89 {
90 this(policy,bufferPool,true,false);
91 }
92
93 /**
94 * Construct Generator with provided policy and bufferPool
95 *
96 * @param policy
97 * the policy to use
98 * @param bufferPool
99 * the buffer pool to use
100 * @param validating
101 * true to enable RFC frame validation
102 */
103 public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating)
104 {
105 this(policy,bufferPool,validating,false);
106 }
107
108 /**
109 * Construct Generator with provided policy and bufferPool
110 *
111 * @param policy
112 * the policy to use
113 * @param bufferPool
114 * the buffer pool to use
115 * @param validating
116 * true to enable RFC frame validation
117 * @param readOnly
118 * true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use.
119 */
120 public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating, boolean readOnly)
121 {
122 this.behavior = policy.getBehavior();
123 this.bufferPool = bufferPool;
124 this.validating = validating;
125 this.readOnly = readOnly;
126 }
127
128 public void assertFrameValid(Frame frame)
129 {
130 if (!validating)
131 {
132 return;
133 }
134
135 /*
136 * RFC 6455 Section 5.2
137 *
138 * MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated
139 * extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
140 */
141 if (frame.isRsv1() && !isRsv1InUse())
142 {
143 throw new ProtocolException("RSV1 not allowed to be set");
144 }
145
146 if (frame.isRsv2() && !isRsv2InUse())
147 {
148 throw new ProtocolException("RSV2 not allowed to be set");
149 }
150
151 if (frame.isRsv3() && !isRsv3InUse())
152 {
153 throw new ProtocolException("RSV3 not allowed to be set");
154 }
155
156 if (OpCode.isControlFrame(frame.getOpCode()))
157 {
158 /*
159 * RFC 6455 Section 5.5
160 *
161 * All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
162 */
163 if (frame.getPayloadLength() > 125)
164 {
165 throw new ProtocolException("Invalid control frame payload length");
166 }
167
168 if (!frame.isFin())
169 {
170 throw new ProtocolException("Control Frames must be FIN=true");
171 }
172
173 /*
174 * RFC 6455 Section 5.5.1
175 *
176 * close frame payload is specially formatted which is checked in CloseInfo
177 */
178 if (frame.getOpCode() == OpCode.CLOSE)
179 {
180
181 ByteBuffer payload = frame.getPayload();
182 if (payload != null)
183 {
184 new CloseInfo(payload,true);
185 }
186 }
187 }
188 }
189
190 public void configureFromExtensions(List<? extends Extension> exts)
191 {
192 // default
193 flagsInUse = 0x00;
194
195 // configure from list of extensions in use
196 for (Extension ext : exts)
197 {
198 if (ext.isRsv1User())
199 {
200 flagsInUse = (byte)(flagsInUse | 0x40);
201 }
202 if (ext.isRsv2User())
203 {
204 flagsInUse = (byte)(flagsInUse | 0x20);
205 }
206 if (ext.isRsv3User())
207 {
208 flagsInUse = (byte)(flagsInUse | 0x10);
209 }
210 }
211 }
212
213 public ByteBuffer generateHeaderBytes(Frame frame)
214 {
215 ByteBuffer buffer = bufferPool.acquire(MAX_HEADER_LENGTH,true);
216 generateHeaderBytes(frame,buffer);
217 return buffer;
218 }
219
220 public void generateHeaderBytes(Frame frame, ByteBuffer buffer)
221 {
222 int p = BufferUtil.flipToFill(buffer);
223
224 // we need a framing header
225 assertFrameValid(frame);
226
227 /*
228 * start the generation process
229 */
230 byte b = 0x00;
231
232 // Setup fin thru opcode
233 if (frame.isFin())
234 {
235 b |= 0x80; // 1000_0000
236 }
237
238 // Set the flags
239 if (frame.isRsv1())
240 {
241 b |= 0x40; // 0100_0000
242 }
243 if (frame.isRsv2())
244 {
245 b |= 0x20; // 0010_0000
246 }
247 if (frame.isRsv3())
248 {
249 b |= 0x10; // 0001_0000
250 }
251
252 // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
253 byte opcode = frame.getOpCode();
254
255 if (frame.getOpCode() == OpCode.CONTINUATION)
256 {
257 // Continuations are not the same OPCODE
258 opcode = OpCode.CONTINUATION;
259 }
260
261 b |= opcode & 0x0F;
262
263 buffer.put(b);
264
265 // is masked
266 b = (frame.isMasked()?(byte)0x80:(byte)0x00);
267
268 // payload lengths
269 int payloadLength = frame.getPayloadLength();
270
271 /*
272 * if length is over 65535 then its a 7 + 64 bit length
273 */
274 if (payloadLength > 0xFF_FF)
275 {
276 // we have a 64 bit length
277 b |= 0x7F;
278 buffer.put(b); // indicate 8 byte length
279 buffer.put((byte)0); //
280 buffer.put((byte)0); // anything over an
281 buffer.put((byte)0); // int is just
282 buffer.put((byte)0); // insane!
283 buffer.put((byte)((payloadLength >> 24) & 0xFF));
284 buffer.put((byte)((payloadLength >> 16) & 0xFF));
285 buffer.put((byte)((payloadLength >> 8) & 0xFF));
286 buffer.put((byte)(payloadLength & 0xFF));
287 }
288 /*
289 * if payload is greater that 126 we have a 7 + 16 bit length
290 */
291 else if (payloadLength >= 0x7E)
292 {
293 b |= 0x7E;
294 buffer.put(b); // indicate 2 byte length
295 buffer.put((byte)(payloadLength >> 8));
296 buffer.put((byte)(payloadLength & 0xFF));
297 }
298 /*
299 * we have a 7 bit length
300 */
301 else
302 {
303 b |= (payloadLength & 0x7F);
304 buffer.put(b);
305 }
306
307 // masking key
308 if (frame.isMasked() && !readOnly)
309 {
310 byte[] mask = frame.getMask();
311 buffer.put(mask);
312 int maskInt = 0;
313 for (byte maskByte : mask)
314 maskInt = (maskInt << 8) + (maskByte & 0xFF);
315
316 // perform data masking here
317 ByteBuffer payload = frame.getPayload();
318 if ((payload != null) && (payload.remaining() > 0))
319 {
320 int maskOffset = 0;
321 int start = payload.position();
322 int end = payload.limit();
323 int remaining;
324 while ((remaining = end - start) > 0)
325 {
326 if (remaining >= 4)
327 {
328 payload.putInt(start,payload.getInt(start) ^ maskInt);
329 start += 4;
330 }
331 else
332 {
333 payload.put(start,(byte)(payload.get(start) ^ mask[maskOffset & 3]));
334 ++start;
335 ++maskOffset;
336 }
337 }
338 }
339 }
340
341 BufferUtil.flipToFlush(buffer,p);
342 }
343
344 /**
345 * Generate the whole frame (header + payload copy) into a single ByteBuffer.
346 * <p>
347 * Note: This is slow, moves lots of memory around. Only use this if you must (such as in unit testing).
348 *
349 * @param frame
350 * the frame to generate
351 * @param buf
352 * the buffer to output the generated frame to
353 */
354 public void generateWholeFrame(Frame frame, ByteBuffer buf)
355 {
356 buf.put(generateHeaderBytes(frame));
357 if (frame.hasPayload())
358 {
359 if (readOnly)
360 {
361 buf.put(frame.getPayload().slice());
362 }
363 else
364 {
365 buf.put(frame.getPayload());
366 }
367 }
368 }
369
370 public ByteBufferPool getBufferPool()
371 {
372 return bufferPool;
373 }
374
375 public void setRsv1InUse(boolean rsv1InUse)
376 {
377 if (readOnly)
378 {
379 throw new RuntimeException("Not allowed to modify read-only frame");
380 }
381 flagsInUse = (byte)((flagsInUse & 0xBF) | (rsv1InUse?0x40:0x00));
382 }
383
384 public void setRsv2InUse(boolean rsv2InUse)
385 {
386 if (readOnly)
387 {
388 throw new RuntimeException("Not allowed to modify read-only frame");
389 }
390 flagsInUse = (byte)((flagsInUse & 0xDF) | (rsv2InUse?0x20:0x00));
391 }
392
393 public void setRsv3InUse(boolean rsv3InUse)
394 {
395 if (readOnly)
396 {
397 throw new RuntimeException("Not allowed to modify read-only frame");
398 }
399 flagsInUse = (byte)((flagsInUse & 0xEF) | (rsv3InUse?0x10:0x00));
400 }
401
402 public boolean isRsv1InUse()
403 {
404 return (flagsInUse & 0x40) != 0;
405 }
406
407 public boolean isRsv2InUse()
408 {
409 return (flagsInUse & 0x20) != 0;
410 }
411
412 public boolean isRsv3InUse()
413 {
414 return (flagsInUse & 0x10) != 0;
415 }
416
417 @Override
418 public String toString()
419 {
420 StringBuilder builder = new StringBuilder();
421 builder.append("Generator[");
422 builder.append(behavior);
423 if (validating)
424 {
425 builder.append(",validating");
426 }
427 if (isRsv1InUse())
428 {
429 builder.append(",+rsv1");
430 }
431 if (isRsv2InUse())
432 {
433 builder.append(",+rsv2");
434 }
435 if (isRsv3InUse())
436 {
437 builder.append(",+rsv3");
438 }
439 builder.append("]");
440 return builder.toString();
441 }
442 }