View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2011 Intalio, Inc.
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    *
8    *   The Eclipse Public License is available at
9    *   http://www.eclipse.org/legal/epl-v10.html
10   *
11   *   The Apache License v2.0 is available at
12   *   http://www.opensource.org/licenses/apache2.0.php
13   *
14   * You may elect to redistribute this code under either of these licenses.
15   *******************************************************************************/
16  // ========================================================================
17  // Copyright (c) 2010 Mort Bay Consulting Pty. Ltd.
18  // ------------------------------------------------------------------------
19  // All rights reserved. This program and the accompanying materials
20  // are made available under the terms of the Eclipse Public License v1.0
21  // and Apache License v2.0 which accompanies this distribution.
22  // The Eclipse Public License is available at
23  // http://www.eclipse.org/legal/epl-v10.html
24  // The Apache License v2.0 is available at
25  // http://www.opensource.org/licenses/apache2.0.php
26  // You may elect to redistribute this code under either of these licenses.
27  // ========================================================================
28  
29  package org.eclipse.jetty.websocket;
30  
31  import java.io.IOException;
32  import java.security.MessageDigest;
33  import java.security.NoSuchAlgorithmException;
34  import java.util.Collections;
35  import java.util.List;
36  
37  import org.eclipse.jetty.io.AbstractConnection;
38  import org.eclipse.jetty.io.AsyncEndPoint;
39  import org.eclipse.jetty.io.Buffer;
40  import org.eclipse.jetty.io.ByteArrayBuffer;
41  import org.eclipse.jetty.io.Connection;
42  import org.eclipse.jetty.io.EndPoint;
43  import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
44  import org.eclipse.jetty.util.StringUtil;
45  import org.eclipse.jetty.util.log.Log;
46  import org.eclipse.jetty.util.log.Logger;
47  import org.eclipse.jetty.websocket.WebSocket.OnFrame;
48  
49  public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
50  {
51      private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
52  
53      public final static byte LENGTH_FRAME=(byte)0x80;
54      public final static byte SENTINEL_FRAME=(byte)0x00;
55  
56      private final WebSocketParser _parser;
57      private final WebSocketGenerator _generator;
58      private final WebSocket _websocket;
59      private final String _protocol;
60      private String _key1;
61      private String _key2;
62      private ByteArrayBuffer _hixieBytes;
63  
64      public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
65          throws IOException
66      {
67          super(endpoint,timestamp);
68  
69          _endp.setMaxIdleTime(maxIdleTime);
70  
71          _websocket = websocket;
72          _protocol=protocol;
73  
74          _generator = new WebSocketGeneratorD00(buffers, _endp);
75          _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
76      }
77  
78      /* ------------------------------------------------------------ */
79      public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
80      {
81          return this;
82      }
83  
84  
85      /* ------------------------------------------------------------ */
86      public void setHixieKeys(String key1,String key2)
87      {
88          _key1=key1;
89          _key2=key2;
90          _hixieBytes=new IndirectNIOBuffer(16);
91      }
92  
93      /* ------------------------------------------------------------ */
94      public Connection handle() throws IOException
95      {
96          try
97          {
98              // handle stupid hixie random bytes
99              if (_hixieBytes!=null)
100             {
101 
102                 // take any available bytes from the parser buffer, which may have already been read
103                 Buffer buffer=_parser.getBuffer();
104                 if (buffer!=null && buffer.length()>0)
105                 {
106                     int l=buffer.length();
107                     if (l>(8-_hixieBytes.length()))
108                         l=8-_hixieBytes.length();
109                     _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
110                     buffer.skip(l);
111                 }
112 
113                 // while we are not blocked
114                 while(_endp.isOpen())
115                 {
116                     // do we now have enough
117                     if (_hixieBytes.length()==8)
118                     {
119                         // we have the silly random bytes
120                         // so let's work out the stupid 16 byte reply.
121                         doTheHixieHixieShake();
122                         _endp.flush(_hixieBytes);
123                         _hixieBytes=null;
124                         _endp.flush();
125                         break;
126                     }
127 
128                     // no, then let's fill
129                     int filled=_endp.fill(_hixieBytes);
130                     if (filled<0)
131                     {
132                         _endp.flush();
133                         _endp.close();
134                         break;
135                     }
136                 }
137 
138                 if (_websocket instanceof OnFrame)
139                     ((OnFrame)_websocket).onHandshake(this);
140                 _websocket.onOpen(this);
141                 return this;
142             }
143 
144             // handle the framing protocol
145             boolean progress=true;
146 
147             while (progress)
148             {
149                 int flushed=_generator.flush();
150                 int filled=_parser.parseNext();
151 
152                 progress = flushed>0 || filled>0;
153 
154                 _endp.flush();
155 
156                 if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
157                     progress=true;
158             }
159         }
160         catch(IOException e)
161         {
162             LOG.debug(e);
163             try
164             {
165                 if (_endp.isOpen())
166                     _endp.close();
167             }
168             catch(IOException e2)
169             {
170                 LOG.ignore(e2);
171             }
172             throw e;
173         }
174         finally
175         {
176             if (_endp.isOpen())
177             {
178                 if (_endp.isInputShutdown() && _generator.isBufferEmpty())
179                     _endp.close();
180                 else
181                     checkWriteable();
182 
183                 checkWriteable();
184             }
185         }
186         return this;
187     }
188 
189     /* ------------------------------------------------------------ */
190     public void onInputShutdown() throws IOException
191     {
192         // TODO
193     }
194 
195     /* ------------------------------------------------------------ */
196     private void doTheHixieHixieShake()
197     {
198         byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
199                 WebSocketConnectionD00.hixieCrypt(_key1),
200                 WebSocketConnectionD00.hixieCrypt(_key2),
201                 _hixieBytes.asArray());
202         _hixieBytes.clear();
203         _hixieBytes.put(result);
204     }
205 
206     /* ------------------------------------------------------------ */
207     public boolean isOpen()
208     {
209         return _endp!=null&&_endp.isOpen();
210     }
211 
212     /* ------------------------------------------------------------ */
213     public boolean isIdle()
214     {
215         return _parser.isBufferEmpty() && _generator.isBufferEmpty();
216     }
217 
218     /* ------------------------------------------------------------ */
219     public boolean isSuspended()
220     {
221         return false;
222     }
223 
224     /* ------------------------------------------------------------ */
225     public void onClose()
226     {
227         _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
228     }
229 
230     /* ------------------------------------------------------------ */
231     /**
232      */
233     public void sendMessage(String content) throws IOException
234     {
235         byte[] data = content.getBytes(StringUtil.__UTF8);
236         _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
237         _generator.flush();
238         checkWriteable();
239     }
240 
241     /* ------------------------------------------------------------ */
242     public void sendMessage(byte[] data, int offset, int length) throws IOException
243     {
244         _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
245         _generator.flush();
246         checkWriteable();
247     }
248 
249     /* ------------------------------------------------------------ */
250     public boolean isMore(byte flags)
251     {
252         return (flags&0x8) != 0;
253     }
254 
255     /* ------------------------------------------------------------ */
256     /**
257      * {@inheritDoc}
258      */
259     public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
260     {
261     }
262 
263     /* ------------------------------------------------------------ */
264     public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
265     {
266         _generator.addFrame((byte)0,opcode,content,offset,length);
267         _generator.flush();
268         checkWriteable();
269     }
270 
271     /* ------------------------------------------------------------ */
272     public void close(int code, String message)
273     {
274         throw new UnsupportedOperationException();
275     }
276 
277     /* ------------------------------------------------------------ */
278     public void disconnect()
279     {
280         close();
281     }
282 
283     /* ------------------------------------------------------------ */
284     public void close()
285     {
286         try
287         {
288             _generator.flush();
289             _endp.close();
290         }
291         catch(IOException e)
292         {
293             LOG.ignore(e);
294         }
295     }
296 
297     public void shutdown()
298     {
299         close();
300     }
301 
302     /* ------------------------------------------------------------ */
303     public void fillBuffersFrom(Buffer buffer)
304     {
305         _parser.fill(buffer);
306     }
307 
308 
309     /* ------------------------------------------------------------ */
310     private void checkWriteable()
311     {
312         if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
313             ((AsyncEndPoint)_endp).scheduleWrite();
314     }
315 
316     /* ------------------------------------------------------------ */
317     static long hixieCrypt(String key)
318     {
319         // Don't ask me what all this is about.
320         // I think it's pretend secret stuff, kind of
321         // like talking in pig latin!
322         long number=0;
323         int spaces=0;
324         for (char c : key.toCharArray())
325         {
326             if (Character.isDigit(c))
327                 number=number*10+(c-'0');
328             else if (c==' ')
329                 spaces++;
330         }
331         return number/spaces;
332     }
333 
334     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
335     {
336         try
337         {
338             MessageDigest md = MessageDigest.getInstance("MD5");
339             byte [] fodder = new byte[16];
340 
341             fodder[0]=(byte)(0xff&(key1>>24));
342             fodder[1]=(byte)(0xff&(key1>>16));
343             fodder[2]=(byte)(0xff&(key1>>8));
344             fodder[3]=(byte)(0xff&key1);
345             fodder[4]=(byte)(0xff&(key2>>24));
346             fodder[5]=(byte)(0xff&(key2>>16));
347             fodder[6]=(byte)(0xff&(key2>>8));
348             fodder[7]=(byte)(0xff&key2);
349             System.arraycopy(key3, 0, fodder, 8, 8);
350             md.update(fodder);
351             return md.digest();
352         }
353         catch (NoSuchAlgorithmException e)
354         {
355             throw new IllegalStateException(e);
356         }
357     }
358 
359     public void setMaxTextMessageSize(int size)
360     {
361     }
362 
363     public void setMaxIdleTime(int ms)
364     {
365         try
366         {
367             _endp.setMaxIdleTime(ms);
368         }
369         catch(IOException e)
370         {
371             LOG.warn(e);
372         }
373     }
374 
375     public void setMaxBinaryMessageSize(int size)
376     {
377     }
378 
379     public int getMaxTextMessageSize()
380     {
381         return -1;
382     }
383 
384     public int getMaxIdleTime()
385     {
386         return _endp.getMaxIdleTime();
387     }
388 
389     public int getMaxBinaryMessageSize()
390     {
391         return -1;
392     }
393 
394     public String getProtocol()
395     {
396         return _protocol;
397     }
398 
399     protected void onFrameHandshake()
400     {
401         if (_websocket instanceof OnFrame)
402         {
403             ((OnFrame)_websocket).onHandshake(this);
404         }
405     }
406 
407     protected void onWebsocketOpen()
408     {
409         _websocket.onOpen(this);
410     }
411 
412     static class FrameHandlerD00 implements WebSocketParser.FrameHandler
413     {
414         final WebSocket _websocket;
415 
416         FrameHandlerD00(WebSocket websocket)
417         {
418             _websocket=websocket;
419         }
420 
421         public void onFrame(byte flags, byte opcode, Buffer buffer)
422         {
423             try
424             {
425                 byte[] array=buffer.array();
426 
427                 if (opcode==0)
428                 {
429                     if (_websocket instanceof WebSocket.OnTextMessage)
430                         ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
431                 }
432                 else
433                 {
434                     if (_websocket instanceof WebSocket.OnBinaryMessage)
435                         ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
436                 }
437             }
438             catch(Throwable th)
439             {
440                 LOG.warn(th);
441             }
442         }
443 
444         public void close(int code,String message)
445         {
446         }
447     }
448 
449     public boolean isMessageComplete(byte flags)
450     {
451         return true;
452     }
453 
454     public byte binaryOpcode()
455     {
456         return LENGTH_FRAME;
457     }
458 
459     public byte textOpcode()
460     {
461         return SENTINEL_FRAME;
462     }
463 
464     public boolean isControl(byte opcode)
465     {
466         return false;
467     }
468 
469     public boolean isText(byte opcode)
470     {
471         return (opcode&LENGTH_FRAME)==0;
472     }
473 
474     public boolean isBinary(byte opcode)
475     {
476         return (opcode&LENGTH_FRAME)!=0;
477     }
478 
479     public boolean isContinuation(byte opcode)
480     {
481         return false;
482     }
483 
484     public boolean isClose(byte opcode)
485     {
486         return false;
487     }
488 
489     public boolean isPing(byte opcode)
490     {
491         return false;
492     }
493 
494     public boolean isPong(byte opcode)
495     {
496         return false;
497     }
498 
499     public List<Extension> getExtensions()
500     {
501         return Collections.emptyList();
502     }
503 
504     public byte continuationOpcode()
505     {
506         return 0;
507     }
508 
509     public byte finMask()
510     {
511         return 0;
512     }
513 
514     public void setAllowFrameFragmentation(boolean allowFragmentation)
515     {
516     }
517 
518     public boolean isAllowFrameFragmentation()
519     {
520         return false;
521     }
522 }