View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.util.log.Log;
27  import org.eclipse.jetty.util.log.Logger;
28  import org.eclipse.jetty.websocket.api.ProtocolException;
29  import org.eclipse.jetty.websocket.api.WebSocketBehavior;
30  import org.eclipse.jetty.websocket.api.WebSocketPolicy;
31  import org.eclipse.jetty.websocket.api.extensions.Extension;
32  import org.eclipse.jetty.websocket.api.extensions.Frame;
33  
34  /**
35   * Generating a frame in WebSocket land.
36   * 
37   * <pre>
38   *    0                   1                   2                   3
39   *    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
40   *   +-+-+-+-+-------+-+-------------+-------------------------------+
41   *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
42   *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
43   *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
44   *   | |1|2|3|       |K|             |                               |
45   *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
46   *   |     Extended payload length continued, if payload len == 127  |
47   *   + - - - - - - - - - - - - - - - +-------------------------------+
48   *   |                               |Masking-key, if MASK set to 1  |
49   *   +-------------------------------+-------------------------------+
50   *   | Masking-key (continued)       |          Payload Data         |
51   *   +-------------------------------- - - - - - - - - - - - - - - - +
52   *   :                     Payload Data continued ...                :
53   *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
54   *   |                     Payload Data continued ...                |
55   *   +---------------------------------------------------------------+
56   * </pre>
57   */
58  public class Generator
59  {
60      private static final Logger LOG = Log.getLogger(Generator.class);
61      /**
62       * The overhead (maximum) for a framing header. Assuming a maximum sized payload with masking key.
63       */
64      public static final int OVERHEAD = 28;
65  
66      private final WebSocketBehavior behavior;
67      private final ByteBufferPool bufferPool;
68      private boolean validating;
69  
70      /** Is there an extension using RSV1 */
71      private boolean rsv1InUse = false;
72      /** Is there an extension using RSV2 */
73      private boolean rsv2InUse = false;
74      /** Is there an extension using RSV3 */
75      private boolean rsv3InUse = false;
76  
77      /**
78       * Construct Generator with provided policy and bufferPool
79       * 
80       * @param policy
81       *            the policy to use
82       * @param bufferPool
83       *            the buffer pool to use
84       */
85      public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool)
86      {
87          this(policy,bufferPool,true);
88      }
89  
90      /**
91       * Construct Generator with provided policy and bufferPool
92       * 
93       * @param policy
94       *            the policy to use
95       * @param bufferPool
96       *            the buffer pool to use
97       * @param validating
98       *            true to enable RFC frame validation
99       */
100     public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating)
101     {
102         this.behavior = policy.getBehavior();
103         this.bufferPool = bufferPool;
104         this.validating = validating;
105     }
106 
107     public void assertFrameValid(Frame frame)
108     {
109         if (!validating)
110         {
111             return;
112         }
113 
114         /*
115          * RFC 6455 Section 5.2
116          * 
117          * 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
118          * extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
119          */
120         if (!rsv1InUse && frame.isRsv1())
121         {
122             throw new ProtocolException("RSV1 not allowed to be set");
123         }
124 
125         if (!rsv2InUse && frame.isRsv2())
126         {
127             throw new ProtocolException("RSV2 not allowed to be set");
128         }
129 
130         if (!rsv3InUse && frame.isRsv3())
131         {
132             throw new ProtocolException("RSV3 not allowed to be set");
133         }
134 
135         if (frame.getType().isControl())
136         {
137             /*
138              * RFC 6455 Section 5.5
139              * 
140              * All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
141              */
142             if (frame.getPayloadLength() > 125)
143             {
144                 throw new ProtocolException("Invalid control frame payload length");
145             }
146 
147             if (!frame.isFin())
148             {
149                 throw new ProtocolException("Control Frames must be FIN=true");
150             }
151 
152             /*
153              * RFC 6455 Section 5.5.1
154              * 
155              * close frame payload is specially formatted which is checked in CloseInfo
156              */
157             if (frame.getType().getOpCode() == OpCode.CLOSE)
158             {
159 
160                 ByteBuffer payload = frame.getPayload();
161                 if (payload != null)
162                 {
163                     new CloseInfo(payload,true);
164                 }
165             }
166         }
167 
168     }
169 
170     public void configureFromExtensions(List<? extends Extension> exts)
171     {
172         // default
173         this.rsv1InUse = false;
174         this.rsv2InUse = false;
175         this.rsv3InUse = false;
176 
177         // configure from list of extensions in use
178         for(Extension ext: exts)
179         {
180             if (ext.isRsv1User())
181             {
182                 this.rsv1InUse = true;
183             }
184             if (ext.isRsv2User())
185             {
186                 this.rsv2InUse = true;
187             }
188             if (ext.isRsv3User())
189             {
190                 this.rsv3InUse = true;
191             }
192         }
193     }
194 
195     /**
196      * generate a byte buffer based on the frame being passed in
197      * 
198      * bufferSize is determined by the length of the payload + 28 for frame overhead
199      * 
200      * @param frame
201      * @return
202      */
203     public synchronized ByteBuffer generate(Frame frame)
204     {
205         int bufferSize = frame.getPayloadLength() + OVERHEAD;
206         return generate(bufferSize,frame);
207     }
208 
209     /**
210      * Generate, into a ByteBuffer, no more than bufferSize of contents from the frame. If the frame exceeds the bufferSize, then multiple calls to
211      * {@link #generate(int, WebSocketFrame)} are required to obtain each window of ByteBuffer to complete the frame.
212      */
213     public synchronized ByteBuffer generate(int windowSize, Frame frame)
214     {
215         if (windowSize < OVERHEAD)
216         {
217             throw new IllegalArgumentException("Cannot have windowSize less than " + OVERHEAD);
218         }
219 
220         LOG.debug("{} Generate: {} (windowSize {})",behavior,frame,windowSize);
221 
222         /*
223          * prepare the byte buffer to put frame into
224          */
225         ByteBuffer buffer = bufferPool.acquire(windowSize,false);
226         BufferUtil.clearToFill(buffer);
227         if (LOG.isDebugEnabled())
228         {
229             LOG.debug("Acquired Buffer (windowSize={}): {}",windowSize,BufferUtil.toDetailString(buffer));
230         }
231         // since the buffer from the pool can exceed the window size, artificially
232         // limit the buffer to the window size.
233         int newlimit = Math.min(buffer.position() + windowSize,buffer.limit());
234         buffer.limit(newlimit);
235         LOG.debug("Buffer limited: {}",buffer);
236 
237         if (frame.remaining() == frame.getPayloadLength())
238         {
239             // we need a framing header
240             assertFrameValid(frame);
241 
242             /*
243              * start the generation process
244              */
245             byte b;
246 
247             // Setup fin thru opcode
248             b = 0x00;
249             if (frame.isFin())
250             {
251                 b |= 0x80; // 1000_0000
252             }
253             if (frame.isRsv1())
254             {
255                 b |= 0x40; // 0100_0000
256             }
257             if (frame.isRsv2())
258             {
259                 b |= 0x20; // 0010_0000
260             }
261             if (frame.isRsv3())
262             {
263                 b |= 0x10;
264             }
265 
266             // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
267             byte opcode = frame.getOpCode();
268 
269             if (frame.isContinuation())
270             {
271                 // Continuations are not the same OPCODE
272                 opcode = OpCode.CONTINUATION;
273             }
274 
275             b |= opcode & 0x0F;
276 
277             buffer.put(b);
278 
279             // is masked
280             b = 0x00;
281             b |= (frame.isMasked()?0x80:0x00);
282 
283             // payload lengths
284             int payloadLength = frame.getPayloadLength();
285 
286             /*
287              * if length is over 65535 then its a 7 + 64 bit length
288              */
289             if (payloadLength > 0xFF_FF)
290             {
291                 // we have a 64 bit length
292                 b |= 0x7F;
293                 buffer.put(b); // indicate 8 byte length
294                 buffer.put((byte)0); //
295                 buffer.put((byte)0); // anything over an
296                 buffer.put((byte)0); // int is just
297                 buffer.put((byte)0); // intsane!
298                 buffer.put((byte)((payloadLength >> 24) & 0xFF));
299                 buffer.put((byte)((payloadLength >> 16) & 0xFF));
300                 buffer.put((byte)((payloadLength >> 8) & 0xFF));
301                 buffer.put((byte)(payloadLength & 0xFF));
302             }
303             /*
304              * if payload is ge 126 we have a 7 + 16 bit length
305              */
306             else if (payloadLength >= 0x7E)
307             {
308                 b |= 0x7E;
309                 buffer.put(b); // indicate 2 byte length
310                 buffer.put((byte)(payloadLength >> 8));
311                 buffer.put((byte)(payloadLength & 0xFF));
312             }
313             /*
314              * we have a 7 bit length
315              */
316             else
317             {
318                 b |= (payloadLength & 0x7F);
319                 buffer.put(b);
320             }
321 
322             // masking key
323             if (frame.isMasked())
324             {
325                 buffer.put(frame.getMask());
326             }
327         }
328 
329         // copy payload
330         if (frame.hasPayload())
331         {
332             // remember the position
333             int maskingStartPosition = buffer.position();
334 
335             // remember the offset within the frame payload (for working with
336             // windowed frames that don't split on 4 byte barriers)
337             int payloadOffset = frame.getPayload().position();
338             int payloadStart = frame.getPayloadStart();
339 
340             // put as much as possible into the buffer
341             BufferUtil.put(frame.getPayload(),buffer);
342 
343             // mask it if needed
344             if (frame.isMasked())
345             {
346                 // move back to remembered position.
347                 int size = buffer.position() - maskingStartPosition;
348                 byte[] mask = frame.getMask();
349                 byte b;
350                 int posBuf;
351                 int posFrame;
352                 for (int i = 0; i < size; i++)
353                 {
354                     posBuf = i + maskingStartPosition;
355                     posFrame = i + (payloadOffset - payloadStart);
356 
357                     // get raw byte from buffer.
358                     b = buffer.get(posBuf);
359 
360                     // mask, using offset information from frame windowing.
361                     b ^= mask[posFrame % 4];
362 
363                     // Mask each byte by its absolute position in the bytebuffer
364                     buffer.put(posBuf,b);
365                 }
366             }
367         }
368 
369         BufferUtil.flipToFlush(buffer,0);
370         if (LOG.isDebugEnabled())
371         {
372             LOG.debug("Generated Buffer: {}",BufferUtil.toDetailString(buffer));
373         }
374         return buffer;
375     }
376 
377     public ByteBufferPool getBufferPool()
378     {
379         return bufferPool;
380     }
381 
382     public boolean isRsv1InUse()
383     {
384         return rsv1InUse;
385     }
386 
387     public boolean isRsv2InUse()
388     {
389         return rsv2InUse;
390     }
391 
392     public boolean isRsv3InUse()
393     {
394         return rsv3InUse;
395     }
396 
397     @Override
398     public String toString()
399     {
400         StringBuilder builder = new StringBuilder();
401         builder.append("Generator[");
402         builder.append(behavior);
403         if (validating)
404         {
405             builder.append(",validating");
406         }
407         if (rsv1InUse)
408         {
409             builder.append(",+rsv1");
410         }
411         if (rsv2InUse)
412         {
413             builder.append(",+rsv2");
414         }
415         if (rsv3InUse)
416         {
417             builder.append(",+rsv3");
418         }
419         builder.append("]");
420         return builder.toString();
421     }
422 }