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