View Javadoc

1   // ========================================================================
2   // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.websocket;
15  
16  import java.io.IOException;
17  import java.io.UnsupportedEncodingException;
18  import java.security.MessageDigest;
19  import java.util.Collections;
20  import java.util.List;
21  
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.eclipse.jetty.io.AbstractConnection;
26  import org.eclipse.jetty.io.AsyncEndPoint;
27  import org.eclipse.jetty.io.Buffer;
28  import org.eclipse.jetty.io.ByteArrayBuffer;
29  import org.eclipse.jetty.io.Connection;
30  import org.eclipse.jetty.io.EndPoint;
31  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
32  import org.eclipse.jetty.util.B64Code;
33  import org.eclipse.jetty.util.StringUtil;
34  import org.eclipse.jetty.util.Utf8Appendable;
35  import org.eclipse.jetty.util.Utf8StringBuilder;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
39  import org.eclipse.jetty.websocket.WebSocket.OnControl;
40  import org.eclipse.jetty.websocket.WebSocket.OnFrame;
41  import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
42  
43  
44  /* ------------------------------------------------------------ */
45  /**
46   * <pre>
47   *    0                   1                   2                   3
48   *    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
49   *   +-+-+-+-+-------+-+-------------+-------------------------------+
50   *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
51   *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
52   *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
53   *   | |1|2|3|       |K|             |                               |
54   *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
55   *   |     Extended payload length continued, if payload len == 127  |
56   *   + - - - - - - - - - - - - - - - +-------------------------------+
57   *   |                               |Masking-key, if MASK set to 1  |
58   *   +-------------------------------+-------------------------------+
59   *   | Masking-key (continued)       |          Payload Data         |
60   *   +-------------------------------- - - - - - - - - - - - - - - - +
61   *   :                     Payload Data continued ...                :
62   *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
63   *   |                     Payload Data continued ...                |
64   *   +---------------------------------------------------------------+
65   * </pre>
66   */
67  public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection
68  {
69      private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class);
70  
71      final static byte OP_CONTINUATION = 0x00;
72      final static byte OP_TEXT = 0x01;
73      final static byte OP_BINARY = 0x02;
74      final static byte OP_EXT_DATA = 0x03;
75  
76      final static byte OP_CONTROL = 0x08;
77      final static byte OP_CLOSE = 0x08;
78      final static byte OP_PING = 0x09;
79      final static byte OP_PONG = 0x0A;
80      final static byte OP_EXT_CTRL = 0x0B;
81  
82      final static int CLOSE_NORMAL=1000;
83      final static int CLOSE_SHUTDOWN=1001;
84      final static int CLOSE_PROTOCOL=1002;
85      final static int CLOSE_BAD_DATA=1003;
86      final static int CLOSE_UNDEFINED=1004;
87      final static int CLOSE_NO_CODE=1005;
88      final static int CLOSE_NO_CLOSE=1006;
89      final static int CLOSE_BAD_PAYLOAD=1007;
90      final static int CLOSE_POLICY_VIOLATION=1008;
91      final static int CLOSE_MESSAGE_TOO_LARGE=1009;
92      final static int CLOSE_REQUIRED_EXTENSION=1010;
93  
94      final static int FLAG_FIN=0x8;
95  
96      final static int VERSION=13;
97  
98      static boolean isLastFrame(byte flags)
99      {
100         return (flags&FLAG_FIN)!=0;
101     }
102 
103     static boolean isControlFrame(byte opcode)
104     {
105         return (opcode&OP_CONTROL)!=0;
106     }
107 
108     private final static byte[] MAGIC;
109     private final List<Extension> _extensions;
110     private final WebSocketParserD13 _parser;
111     private final WebSocketGeneratorD13 _generator;
112     private final WebSocketGenerator _outbound;
113     private final WebSocket _webSocket;
114     private final OnFrame _onFrame;
115     private final OnBinaryMessage _onBinaryMessage;
116     private final OnTextMessage _onTextMessage;
117     private final OnControl _onControl;
118     private final String _protocol;
119     private final int _draft;
120     private final ClassLoader _context;
121     private volatile int _closeCode;
122     private volatile String _closeMessage;
123     private volatile boolean _closedIn;
124     private volatile boolean _closedOut;
125     private int _maxTextMessageSize=-1;
126     private int _maxBinaryMessageSize=-1;
127 
128     static
129     {
130         try
131         {
132             MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
133         }
134         catch (UnsupportedEncodingException e)
135         {
136             throw new RuntimeException(e);
137         }
138     }
139 
140     private final WebSocket.FrameConnection _connection = new WSFrameConnection();
141 
142 
143     /* ------------------------------------------------------------ */
144     public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
145         throws IOException
146     {
147         this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
148     }
149 
150     /* ------------------------------------------------------------ */
151     public WebSocketConnectionD13(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
152         throws IOException
153     {
154         super(endpoint,timestamp);
155 
156         _context=Thread.currentThread().getContextClassLoader();
157 
158         _draft=draft;
159         _endp.setMaxIdleTime(maxIdleTime);
160 
161         _webSocket = websocket;
162         _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
163         _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
164         _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
165         _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
166         _generator = new WebSocketGeneratorD13(buffers, _endp,maskgen);
167 
168         _extensions=extensions;
169         WebSocketParser.FrameHandler frameHandler = new WSFrameHandler();
170         if (_extensions!=null)
171         {
172             int e=0;
173             for (Extension extension : _extensions)
174             {
175                 extension.bind(
176                         _connection,
177                         e==extensions.size()-1? frameHandler :extensions.get(e+1),
178                         e==0?_generator:extensions.get(e-1));
179                 e++;
180             }
181         }
182 
183         _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
184         WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0);
185 
186         _parser = new WebSocketParserD13(buffers, endpoint, inbound,maskgen==null);
187 
188         _protocol=protocol;
189 
190     }
191 
192     /* ------------------------------------------------------------ */
193     public WebSocket.Connection getConnection()
194     {
195         return _connection;
196     }
197 
198     /* ------------------------------------------------------------ */
199     public List<Extension> getExtensions()
200     {
201         if (_extensions==null)
202             return Collections.emptyList();
203 
204         return _extensions;
205     }
206 
207     /* ------------------------------------------------------------ */
208     public Connection handle() throws IOException
209     {
210         Thread current = Thread.currentThread();
211         ClassLoader oldcontext = current.getContextClassLoader();
212         current.setContextClassLoader(_context);
213         try
214         {
215             // handle the framing protocol
216             boolean progress=true;
217 
218             while (progress)
219             {
220                 int flushed=_generator.flushBuffer();
221                 int filled=_parser.parseNext();
222 
223                 progress = flushed>0 || filled>0;
224 
225                 _endp.flush();
226 
227                 if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
228                     progress=true;
229             }
230         }
231         catch(IOException e)
232         {
233             try
234             {
235                 if (_endp.isOpen())
236                     _endp.close();
237             }
238             catch(IOException e2)
239             {
240                 LOG.ignore(e2);
241             }
242             throw e;
243         }
244         finally
245         {
246             current.setContextClassLoader(oldcontext);
247             _parser.returnBuffer();
248             _generator.returnBuffer();
249             if (_endp.isOpen())
250             {
251                 if (_closedIn && _closedOut && _outbound.isBufferEmpty())
252                     _endp.close();
253                 else if (_endp.isInputShutdown() && !_closedIn)
254                     closeIn(CLOSE_NO_CLOSE,null);
255                 else
256                     checkWriteable();
257             }
258         }
259         return this;
260     }
261 
262     /* ------------------------------------------------------------ */
263     public void onInputShutdown() throws IOException
264     {
265         if (!_closedIn)
266             _endp.close();
267     }
268 
269     /* ------------------------------------------------------------ */
270     public boolean isIdle()
271     {
272         return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
273     }
274 
275     /* ------------------------------------------------------------ */
276     @Override
277     public void onIdleExpired()
278     {
279         long idle = System.currentTimeMillis()-((SelectChannelEndPoint)_endp).getIdleTimestamp();
280         closeOut(WebSocketConnectionD13.CLOSE_NORMAL,"Idle for "+idle+"ms > "+_endp.getMaxIdleTime()+"ms");
281     }
282 
283     /* ------------------------------------------------------------ */
284     public boolean isSuspended()
285     {
286         return false;
287     }
288 
289     /* ------------------------------------------------------------ */
290     public void onClose()
291     {
292         final boolean closed;
293         synchronized (this)
294         {
295             closed=_closeCode==0;
296             if (closed)
297                 _closeCode=WebSocketConnectionD13.CLOSE_NO_CLOSE;
298         }
299         if (closed)
300             _webSocket.onClose(WebSocketConnectionD13.CLOSE_NO_CLOSE,"closed");
301     }
302 
303     /* ------------------------------------------------------------ */
304     public void closeIn(int code,String message)
305     {
306         LOG.debug("ClosedIn {} {} {}",this,code,message);
307 
308         final boolean closed_out;
309         final boolean tell_app;
310         synchronized (this)
311         {
312             closed_out=_closedOut;
313             _closedIn=true;
314             tell_app=_closeCode==0;
315             if (tell_app)
316             {
317                 _closeCode=code;
318                 _closeMessage=message;
319             }
320         }
321 
322         try
323         {
324             if (tell_app)
325                 _webSocket.onClose(code,message);
326         }
327         finally
328         {
329             if (!closed_out)
330                 closeOut(code,message);
331         }
332     }
333 
334     /* ------------------------------------------------------------ */
335     public void closeOut(int code,String message)
336     {
337         LOG.debug("ClosedOut {} {} {}",this,code,message);
338 
339         final boolean closed_out;
340         final boolean tell_app;
341         synchronized (this)
342         {
343             closed_out=_closedOut;
344             _closedOut=true;
345             tell_app=_closeCode==0;
346             if (tell_app)
347             {
348                 _closeCode=code;
349                 _closeMessage=message;
350             }
351         }
352 
353         try
354         {
355             if (tell_app)
356                 _webSocket.onClose(code,message);
357         }
358         finally
359         {
360             try
361             {
362                 if (!closed_out)
363                 {
364                     // Close code 1005/1006 are never to be sent as a status over
365                     // a Close control frame. Code<-1 also means no node.
366 
367                     if (code<0 || (code == WebSocketConnectionD13.CLOSE_NO_CODE) || code==WebSocketConnectionD13.CLOSE_NO_CLOSE)
368                         code=-1;
369                     else if (code==0)
370                         code=WebSocketConnectionD13.CLOSE_NORMAL;
371 
372                     byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
373                     bytes[0]=(byte)(code/0x100);
374                     bytes[1]=(byte)(code%0x100);
375                     _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_CLOSE,bytes,0,code>0?bytes.length:0);
376                     _outbound.flush();
377                 }
378             }
379             catch(IOException e)
380             {
381                 LOG.ignore(e);
382             }
383         }
384     }
385 
386     /* ------------------------------------------------------------ */
387     public void fillBuffersFrom(Buffer buffer)
388     {
389         _parser.fill(buffer);
390     }
391 
392     /* ------------------------------------------------------------ */
393     private void checkWriteable()
394     {
395         if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
396         {
397             ((AsyncEndPoint)_endp).scheduleWrite();
398         }
399     }
400 
401     /* ------------------------------------------------------------ */
402     /* ------------------------------------------------------------ */
403     /* ------------------------------------------------------------ */
404     private class WSFrameConnection implements WebSocket.FrameConnection
405     {
406         volatile boolean _disconnecting;
407 
408         /* ------------------------------------------------------------ */
409         public void sendMessage(String content) throws IOException
410         {
411             if (_closedOut)
412                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
413             byte[] data = content.getBytes(StringUtil.__UTF8);
414             _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_TEXT,data,0,data.length);
415             checkWriteable();
416         }
417 
418         /* ------------------------------------------------------------ */
419         public void sendMessage(byte[] content, int offset, int length) throws IOException
420         {
421             if (_closedOut)
422                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
423             _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_BINARY,content,offset,length);
424             checkWriteable();
425         }
426 
427         /* ------------------------------------------------------------ */
428         public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
429         {
430             if (_closedOut)
431                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
432             _outbound.addFrame(flags,opcode,content,offset,length);
433             checkWriteable();
434         }
435 
436         /* ------------------------------------------------------------ */
437         public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
438         {
439             // TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented
440             if (_closedOut)
441                 throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
442             _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
443             checkWriteable();
444         }
445 
446         /* ------------------------------------------------------------ */
447         public boolean isMessageComplete(byte flags)
448         {
449             return isLastFrame(flags);
450         }
451 
452         /* ------------------------------------------------------------ */
453         public boolean isOpen()
454         {
455             return _endp!=null&&_endp.isOpen();
456         }
457 
458         /* ------------------------------------------------------------ */
459         public void close(int code, String message)
460         {
461             if (_disconnecting)
462                 return;
463             _disconnecting=true;
464             WebSocketConnectionD13.this.closeOut(code,message);
465         }
466 
467         /* ------------------------------------------------------------ */
468         public void setMaxIdleTime(int ms)
469         {
470             try
471             {
472                 _endp.setMaxIdleTime(ms);
473             }
474             catch(IOException e)
475             {
476                 LOG.warn(e);
477             }
478         }
479 
480         /* ------------------------------------------------------------ */
481         public void setMaxTextMessageSize(int size)
482         {
483             _maxTextMessageSize=size;
484         }
485 
486         /* ------------------------------------------------------------ */
487         public void setMaxBinaryMessageSize(int size)
488         {
489             _maxBinaryMessageSize=size;
490         }
491 
492         /* ------------------------------------------------------------ */
493         public int getMaxIdleTime()
494         {
495             return _endp.getMaxIdleTime();
496         }
497 
498         /* ------------------------------------------------------------ */
499         public int getMaxTextMessageSize()
500         {
501             return _maxTextMessageSize;
502         }
503 
504         /* ------------------------------------------------------------ */
505         public int getMaxBinaryMessageSize()
506         {
507             return _maxBinaryMessageSize;
508         }
509 
510         /* ------------------------------------------------------------ */
511         public String getProtocol()
512         {
513             return _protocol;
514         }
515 
516         /* ------------------------------------------------------------ */
517         public byte binaryOpcode()
518         {
519             return OP_BINARY;
520         }
521 
522         /* ------------------------------------------------------------ */
523         public byte textOpcode()
524         {
525             return OP_TEXT;
526         }
527 
528         /* ------------------------------------------------------------ */
529         public byte continuationOpcode()
530         {
531             return OP_CONTINUATION;
532         }
533 
534         /* ------------------------------------------------------------ */
535         public byte finMask()
536         {
537             return FLAG_FIN;
538         }
539 
540         /* ------------------------------------------------------------ */
541         public boolean isControl(byte opcode)
542         {
543             return isControlFrame(opcode);
544         }
545 
546         /* ------------------------------------------------------------ */
547         public boolean isText(byte opcode)
548         {
549             return opcode==OP_TEXT;
550         }
551 
552         /* ------------------------------------------------------------ */
553         public boolean isBinary(byte opcode)
554         {
555             return opcode==OP_BINARY;
556         }
557 
558         /* ------------------------------------------------------------ */
559         public boolean isContinuation(byte opcode)
560         {
561             return opcode==OP_CONTINUATION;
562         }
563 
564         /* ------------------------------------------------------------ */
565         public boolean isClose(byte opcode)
566         {
567             return opcode==OP_CLOSE;
568         }
569 
570         /* ------------------------------------------------------------ */
571         public boolean isPing(byte opcode)
572         {
573             return opcode==OP_PING;
574         }
575 
576         /* ------------------------------------------------------------ */
577         public boolean isPong(byte opcode)
578         {
579             return opcode==OP_PONG;
580         }
581 
582         /* ------------------------------------------------------------ */
583         public void disconnect()
584         {
585             close(CLOSE_NORMAL,null);
586         }
587 
588         /* ------------------------------------------------------------ */
589         public void close()
590         {
591             close(CLOSE_NORMAL,null);
592         }
593 
594         /* ------------------------------------------------------------ */
595         public void setAllowFrameFragmentation(boolean allowFragmentation)
596         {
597             _parser.setFakeFragments(allowFragmentation);
598         }
599 
600         /* ------------------------------------------------------------ */
601         public boolean isAllowFrameFragmentation()
602         {
603             return _parser.isFakeFragments();
604         }
605 
606         /* ------------------------------------------------------------ */
607         @Override
608         public String toString()
609         {
610             return this.getClass().getSimpleName()+"D13@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
611         }
612     }
613 
614     /* ------------------------------------------------------------ */
615     /* ------------------------------------------------------------ */
616     /* ------------------------------------------------------------ */
617     private class WSFrameHandler implements WebSocketParser.FrameHandler
618     {
619         private static final int MAX_CONTROL_FRAME_PAYLOAD = 125;
620         private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity
621         private ByteArrayBuffer _aggregate;
622         private byte _opcode=-1;
623 
624         public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
625         {
626             boolean lastFrame = isLastFrame(flags);
627 
628             synchronized(WebSocketConnectionD13.this)
629             {
630                 // Ignore incoming after a close
631                 if (_closedIn)
632                     return;
633             }
634             try
635             {
636                 byte[] array=buffer.array();
637 
638                 if (isControlFrame(opcode) && buffer.length()>MAX_CONTROL_FRAME_PAYLOAD)
639                 {
640                     errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large: " + buffer.length() + " > " + MAX_CONTROL_FRAME_PAYLOAD);
641                     return;
642                 }
643 
644                 // TODO: check extensions for RSV bit(s) meanings
645                 if ((flags&0x7)!=0)
646                 {
647                     errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags));
648                     return;
649                 }
650 
651                 // Ignore all frames after error close
652                 if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
653                 {
654                     return;
655                 }
656 
657                 // Deliver frame if websocket is a FrameWebSocket
658                 if (_onFrame!=null)
659                 {
660                     if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
661                         return;
662                 }
663 
664                 if (_onControl!=null && isControlFrame(opcode))
665                 {
666                     if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
667                         return;
668                 }
669 
670                 switch(opcode)
671                 {
672                     case WebSocketConnectionD13.OP_CONTINUATION:
673                     {
674                         if (_opcode==-1)
675                         {
676                             errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad Continuation");
677                             return;
678                         }
679 
680                         // If text, append to the message buffer
681                         if (_onTextMessage!=null && _opcode==WebSocketConnectionD13.OP_TEXT)
682                         {
683                             if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
684                             {
685                                 // If this is the last fragment, deliver the text buffer
686                                 if (lastFrame)
687                                 {
688                                     _opcode=-1;
689                                     String msg =_utf8.toString();
690                                     _utf8.reset();
691                                     _onTextMessage.onMessage(msg);
692                                 }
693                             }
694                             else
695                                 textMessageTooLarge();
696                         }
697 
698                         if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
699                         {
700                             if (_aggregate!=null && checkBinaryMessageSize(_aggregate.length(),buffer.length()))
701                             {
702                                 _aggregate.put(buffer);
703 
704                                 // If this is the last fragment, deliver
705                                 if (lastFrame && _onBinaryMessage!=null)
706                                 {
707                                     try
708                                     {
709                                         _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
710                                     }
711                                     finally
712                                     {
713                                         _opcode=-1;
714                                         _aggregate.clear();
715                                     }
716                                 }
717                             }
718                         }
719                         break;
720                     }
721                     case WebSocketConnectionD13.OP_PING:
722                     {
723                         LOG.debug("PING {}",this);
724                         if (!_closedOut)
725                         {
726                             _connection.sendControl(WebSocketConnectionD13.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
727                         }
728                         break;
729                     }
730 
731                     case WebSocketConnectionD13.OP_PONG:
732                     {
733                         LOG.debug("PONG {}",this);
734                         break;
735                     }
736 
737                     case WebSocketConnectionD13.OP_CLOSE:
738                     {
739                         int code=WebSocketConnectionD13.CLOSE_NO_CODE;
740                         String message=null;
741                         if (buffer.length()>=2)
742                         {
743                             code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]);
744 
745                             // Validate close status codes.
746                             if (code < WebSocketConnectionD13.CLOSE_NORMAL ||
747                                 code == WebSocketConnectionD13.CLOSE_UNDEFINED ||
748                                 code == WebSocketConnectionD13.CLOSE_NO_CLOSE ||
749                                 code == WebSocketConnectionD13.CLOSE_NO_CODE ||
750                                 ( code > 1010 && code <= 2999 ) ||
751                                 code >= 5000 )
752                             {
753                                 errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Invalid close code " + code);
754                                 return;
755                             }
756 
757                             if (buffer.length()>2)
758                             {
759                                 if(_utf8.append(buffer.array(),buffer.getIndex()+2,buffer.length()-2,_connection.getMaxTextMessageSize()))
760                                 {
761                                     message = _utf8.toString();
762                                     _utf8.reset();
763                                 }
764                             }
765                         }
766                         else if(buffer.length() == 1)
767                         {
768                             // Invalid length. use status code 1002 (Protocol error)
769                             errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Invalid payload length of 1");
770                             return;
771                         }
772                         closeIn(code,message);
773                         break;
774                     }
775 
776                     case WebSocketConnectionD13.OP_TEXT:
777                     {
778                         if (_opcode!=-1)
779                         {
780                             errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
781                             return;
782                         }
783 
784                         if(_onTextMessage!=null)
785                         {
786                             if (_connection.getMaxTextMessageSize()<=0)
787                             {
788                                 // No size limit, so handle only final frames
789                                 if (lastFrame)
790                                     _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
791                                 else
792                                 {
793                                     LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
794                                     errorClose(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled");
795                                 }
796                             }
797                             // append bytes to message buffer (if they fit)
798                             else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
799                             {
800                                 if (lastFrame)
801                                 {
802                                     String msg =_utf8.toString();
803                                     _utf8.reset();
804                                     _onTextMessage.onMessage(msg);
805                                 }
806                                 else
807                                 {
808                                     _opcode=WebSocketConnectionD13.OP_TEXT;
809                                 }
810                             }
811                             else
812                                 textMessageTooLarge();
813                         }
814                         break;
815                     }
816 
817                     case WebSocketConnectionD13.OP_BINARY:
818                     {
819                         if (_opcode!=-1)
820                         {
821                             errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
822                             return;
823                         }
824 
825                         if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
826                         {
827                             if (lastFrame)
828                             {
829                                 _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
830                             }
831                             else if (_connection.getMaxBinaryMessageSize()>=0)
832                             {
833                                 _opcode=opcode;
834                                 // TODO use a growing buffer rather than a fixed one.
835                                 if (_aggregate==null)
836                                     _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
837                                 _aggregate.put(buffer);
838                             }
839                             else
840                             {
841                                 LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
842                                 errorClose(WebSocketConnectionD13.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled");
843                             }
844                         }
845                         break;
846                     }
847 
848                     default:
849                         errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
850                         break;
851                 }
852             }
853             catch(Utf8Appendable.NotUtf8Exception notUtf8)
854             {
855                 LOG.warn("{} for {}",notUtf8,_endp);
856                 LOG.debug(notUtf8);
857                 errorClose(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,"Invalid UTF-8");
858             }
859             catch(Throwable probablyNotUtf8)
860             {
861                 LOG.warn("{} for {}",probablyNotUtf8,_endp);
862                 LOG.debug(probablyNotUtf8);
863                 errorClose(WebSocketConnectionD13.CLOSE_BAD_PAYLOAD,"Invalid Payload: "+probablyNotUtf8);
864             }
865         }
866 
867         private void errorClose(int code, String message)
868         {
869             _connection.close(code,message);
870 
871             // Brutally drop the connection
872             try
873             {
874                 _endp.close();
875             }
876             catch (IOException e)
877             {
878                 LOG.warn(e.toString());
879                 LOG.debug(e);
880             }
881         }
882 
883         private boolean checkBinaryMessageSize(int bufferLen, int length)
884         {
885             int max = _connection.getMaxBinaryMessageSize();
886             if (max>0 && (bufferLen+length)>max)
887             {
888                 LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
889                 _connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
890                 _opcode=-1;
891                 if (_aggregate!=null)
892                     _aggregate.clear();
893                 return false;
894             }
895             return true;
896         }
897 
898         private void textMessageTooLarge()
899         {
900             LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
901             _connection.close(WebSocketConnectionD13.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
902 
903             _opcode=-1;
904             _utf8.reset();
905         }
906 
907         public void close(int code,String message)
908         {
909             if (code!=CLOSE_NORMAL)
910                 LOG.warn("Close: "+code+" "+message);
911             _connection.close(code,message);
912         }
913 
914         @Override
915         public String toString()
916         {
917             return WebSocketConnectionD13.this.toString()+"FH";
918         }
919     }
920     
921     /* ------------------------------------------------------------ */
922     public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
923     {
924         String key = request.getHeader("Sec-WebSocket-Key");
925 
926         response.setHeader("Upgrade","WebSocket");
927         response.addHeader("Connection","Upgrade");
928         response.addHeader("Sec-WebSocket-Accept",hashKey(key));
929         if (subprotocol!=null)
930             response.addHeader("Sec-WebSocket-Protocol",subprotocol);
931 
932         for(Extension ext : _extensions)
933             response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
934 
935         response.sendError(101);
936 
937         if (_onFrame!=null)
938             _onFrame.onHandshake(_connection);
939         _webSocket.onOpen(_connection);
940     }
941 
942     /* ------------------------------------------------------------ */
943     public static String hashKey(String key)
944     {
945         try
946         {
947             MessageDigest md = MessageDigest.getInstance("SHA1");
948             md.update(key.getBytes("UTF-8"));
949             md.update(MAGIC);
950             return new String(B64Code.encode(md.digest()));
951         }
952         catch (Exception e)
953         {
954             throw new RuntimeException(e);
955         }
956     }
957 
958     /* ------------------------------------------------------------ */
959     @Override
960     public String toString()
961     {
962          return "WS/D"+_draft+"-"+_endp;
963     }
964 }