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.close();
133                         break;
134                     }
135                 }
136 
137                 if (_websocket instanceof OnFrame)
138                     ((OnFrame)_websocket).onHandshake(this);
139                 _websocket.onOpen(this);
140                 return this;
141             }
142 
143             // handle the framing protocol
144             boolean progress=true;
145 
146             while (progress)
147             {
148                 int flushed=_generator.flush();
149                 int filled=_parser.parseNext();
150 
151                 progress = flushed>0 || filled>0;
152 
153                 _endp.flush();
154 
155                 if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
156                     progress=true;
157             }
158         }
159         catch(IOException e)
160         {
161             LOG.debug(e);
162             try
163             {
164                 if (_endp.isOpen())
165                     _endp.close();
166             }
167             catch(IOException e2)
168             {
169                 LOG.ignore(e2);
170             }
171             throw e;
172         }
173         finally
174         {
175             if (_endp.isOpen())
176             {
177                 if (_endp.isInputShutdown() && _generator.isBufferEmpty())
178                     _endp.close();
179                 else
180                     checkWriteable();
181 
182                 checkWriteable();
183             }
184         }
185         return this;
186     }
187 
188     /* ------------------------------------------------------------ */
189     public void onInputShutdown() throws IOException
190     {
191         // TODO
192     }
193 
194     /* ------------------------------------------------------------ */
195     private void doTheHixieHixieShake()
196     {
197         byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
198                 WebSocketConnectionD00.hixieCrypt(_key1),
199                 WebSocketConnectionD00.hixieCrypt(_key2),
200                 _hixieBytes.asArray());
201         _hixieBytes.clear();
202         _hixieBytes.put(result);
203     }
204 
205     /* ------------------------------------------------------------ */
206     public boolean isOpen()
207     {
208         return _endp!=null&&_endp.isOpen();
209     }
210 
211     /* ------------------------------------------------------------ */
212     public boolean isIdle()
213     {
214         return _parser.isBufferEmpty() && _generator.isBufferEmpty();
215     }
216 
217     /* ------------------------------------------------------------ */
218     public boolean isSuspended()
219     {
220         return false;
221     }
222 
223     /* ------------------------------------------------------------ */
224     public void onClose()
225     {
226         _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
227     }
228 
229     /* ------------------------------------------------------------ */
230     /**
231      */
232     public void sendMessage(String content) throws IOException
233     {
234         byte[] data = content.getBytes(StringUtil.__UTF8);
235         _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
236         _generator.flush();
237         checkWriteable();
238     }
239 
240     /* ------------------------------------------------------------ */
241     public void sendMessage(byte[] data, int offset, int length) throws IOException
242     {
243         _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
244         _generator.flush();
245         checkWriteable();
246     }
247 
248     /* ------------------------------------------------------------ */
249     public boolean isMore(byte flags)
250     {
251         return (flags&0x8) != 0;
252     }
253 
254     /* ------------------------------------------------------------ */
255     /**
256      * {@inheritDoc}
257      */
258     public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
259     {
260     }
261 
262     /* ------------------------------------------------------------ */
263     public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
264     {
265         _generator.addFrame((byte)0,opcode,content,offset,length);
266         _generator.flush();
267         checkWriteable();
268     }
269 
270     /* ------------------------------------------------------------ */
271     public void close(int code, String message)
272     {
273         throw new UnsupportedOperationException();
274     }
275 
276     /* ------------------------------------------------------------ */
277     public void disconnect()
278     {
279         close();
280     }
281 
282     /* ------------------------------------------------------------ */
283     public void close()
284     {
285         try
286         {
287             _generator.flush();
288             _endp.close();
289         }
290         catch(IOException e)
291         {
292             LOG.ignore(e);
293         }
294     }
295 
296     public void shutdown()
297     {
298         close();
299     }
300 
301     /* ------------------------------------------------------------ */
302     public void fillBuffersFrom(Buffer buffer)
303     {
304         _parser.fill(buffer);
305     }
306 
307 
308     /* ------------------------------------------------------------ */
309     private void checkWriteable()
310     {
311         if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
312             ((AsyncEndPoint)_endp).scheduleWrite();
313     }
314 
315     /* ------------------------------------------------------------ */
316     static long hixieCrypt(String key)
317     {
318         // Don't ask me what all this is about.
319         // I think it's pretend secret stuff, kind of
320         // like talking in pig latin!
321         long number=0;
322         int spaces=0;
323         for (char c : key.toCharArray())
324         {
325             if (Character.isDigit(c))
326                 number=number*10+(c-'0');
327             else if (c==' ')
328                 spaces++;
329         }
330         return number/spaces;
331     }
332 
333     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
334     {
335         try
336         {
337             MessageDigest md = MessageDigest.getInstance("MD5");
338             byte [] fodder = new byte[16];
339 
340             fodder[0]=(byte)(0xff&(key1>>24));
341             fodder[1]=(byte)(0xff&(key1>>16));
342             fodder[2]=(byte)(0xff&(key1>>8));
343             fodder[3]=(byte)(0xff&key1);
344             fodder[4]=(byte)(0xff&(key2>>24));
345             fodder[5]=(byte)(0xff&(key2>>16));
346             fodder[6]=(byte)(0xff&(key2>>8));
347             fodder[7]=(byte)(0xff&key2);
348             System.arraycopy(key3, 0, fodder, 8, 8);
349             md.update(fodder);
350             return md.digest();
351         }
352         catch (NoSuchAlgorithmException e)
353         {
354             throw new IllegalStateException(e);
355         }
356     }
357 
358     public void setMaxTextMessageSize(int size)
359     {
360     }
361 
362     public void setMaxIdleTime(int ms)
363     {
364         try
365         {
366             _endp.setMaxIdleTime(ms);
367         }
368         catch(IOException e)
369         {
370             LOG.warn(e);
371         }
372     }
373 
374     public void setMaxBinaryMessageSize(int size)
375     {
376     }
377 
378     public int getMaxTextMessageSize()
379     {
380         return -1;
381     }
382 
383     public int getMaxIdleTime()
384     {
385         return _endp.getMaxIdleTime();
386     }
387 
388     public int getMaxBinaryMessageSize()
389     {
390         return -1;
391     }
392 
393     public String getProtocol()
394     {
395         return _protocol;
396     }
397 
398     protected void onFrameHandshake()
399     {
400         if (_websocket instanceof OnFrame)
401         {
402             ((OnFrame)_websocket).onHandshake(this);
403         }
404     }
405 
406     protected void onWebsocketOpen()
407     {
408         _websocket.onOpen(this);
409     }
410 
411     static class FrameHandlerD00 implements WebSocketParser.FrameHandler
412     {
413         final WebSocket _websocket;
414 
415         FrameHandlerD00(WebSocket websocket)
416         {
417             _websocket=websocket;
418         }
419 
420         public void onFrame(byte flags, byte opcode, Buffer buffer)
421         {
422             try
423             {
424                 byte[] array=buffer.array();
425 
426                 if (opcode==0)
427                 {
428                     if (_websocket instanceof WebSocket.OnTextMessage)
429                         ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
430                 }
431                 else
432                 {
433                     if (_websocket instanceof WebSocket.OnBinaryMessage)
434                         ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
435                 }
436             }
437             catch(Throwable th)
438             {
439                 LOG.warn(th);
440             }
441         }
442 
443         public void close(int code,String message)
444         {
445         }
446     }
447 
448     public boolean isMessageComplete(byte flags)
449     {
450         return true;
451     }
452 
453     public byte binaryOpcode()
454     {
455         return LENGTH_FRAME;
456     }
457 
458     public byte textOpcode()
459     {
460         return SENTINEL_FRAME;
461     }
462 
463     public boolean isControl(byte opcode)
464     {
465         return false;
466     }
467 
468     public boolean isText(byte opcode)
469     {
470         return (opcode&LENGTH_FRAME)==0;
471     }
472 
473     public boolean isBinary(byte opcode)
474     {
475         return (opcode&LENGTH_FRAME)!=0;
476     }
477 
478     public boolean isContinuation(byte opcode)
479     {
480         return false;
481     }
482 
483     public boolean isClose(byte opcode)
484     {
485         return false;
486     }
487 
488     public boolean isPing(byte opcode)
489     {
490         return false;
491     }
492 
493     public boolean isPong(byte opcode)
494     {
495         return false;
496     }
497 
498     public List<Extension> getExtensions()
499     {
500         return Collections.emptyList();
501     }
502 
503     public byte continuationOpcode()
504     {
505         return 0;
506     }
507 
508     public byte finMask()
509     {
510         return 0;
511     }
512 
513     public void setAllowFrameFragmentation(boolean allowFragmentation)
514     {
515     }
516 
517     public boolean isAllowFrameFragmentation()
518     {
519         return false;
520     }
521 }