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.security.MessageDigest;
18  import java.security.NoSuchAlgorithmException;
19  
20  import org.eclipse.jetty.io.AsyncEndPoint;
21  import org.eclipse.jetty.io.Buffer;
22  import org.eclipse.jetty.io.ByteArrayBuffer;
23  import org.eclipse.jetty.io.Connection;
24  import org.eclipse.jetty.io.EndPoint;
25  import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
26  import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
27  import org.eclipse.jetty.util.log.Log;
28  
29  public class WebSocketConnection implements Connection, WebSocket.Outbound
30  {
31      final IdleCheck _idle;
32      final EndPoint _endp;
33      final WebSocketParser _parser;
34      final WebSocketGenerator _generator;
35      final long _timestamp;
36      final WebSocket _websocket;
37      String _key1;
38      String _key2;
39      ByteArrayBuffer _hixieBytes;
40  
41      public WebSocketConnection(WebSocket websocket, EndPoint endpoint,int draft)
42          throws IOException
43      {
44          this(websocket,endpoint,new WebSocketBuffers(8192),System.currentTimeMillis(),300000,draft);
45      }
46      
47      public WebSocketConnection(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, int draft)
48          throws IOException
49      {
50          // TODO - can we use the endpoint idle mechanism?
51          if (endpoint instanceof AsyncEndPoint)
52              ((AsyncEndPoint)endpoint).cancelIdle();
53          
54          _endp = endpoint;
55          _endp.setMaxIdleTime(maxIdleTime);
56          
57          _timestamp = timestamp;
58          _websocket = websocket;
59  
60          // Select the parser/generators to use
61          switch(draft)
62          {
63              case 1:
64                  _generator = new WebSocketGeneratorD01(buffers, _endp);
65                  _parser = new WebSocketParserD01(buffers, endpoint, new FrameHandlerD1(this,_websocket));
66                  break;
67              default:
68                  _generator = new WebSocketGeneratorD00(buffers, _endp);
69                  _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD0(_websocket));
70          }
71  
72          // TODO should these be AsyncEndPoint checks/calls?
73          if (_endp instanceof SelectChannelEndPoint)
74          {
75              final SelectChannelEndPoint scep=(SelectChannelEndPoint)_endp;
76              scep.cancelIdle();
77              _idle=new IdleCheck()
78              {
79                  public void access(EndPoint endp)
80                  {
81                      scep.scheduleIdle();
82                  }
83              };
84              scep.scheduleIdle();
85          }
86          else
87          {
88              _idle = new IdleCheck()
89              {
90                  public void access(EndPoint endp)
91                  {}
92              };
93          }
94      }
95      
96      public void setHixieKeys(String key1,String key2)
97      {
98          _key1=key1;
99          _key2=key2;
100         _hixieBytes=new IndirectNIOBuffer(16);
101     }
102 
103     public Connection handle() throws IOException
104     {
105         try
106         {
107             // handle stupid hixie random bytes
108             if (_hixieBytes!=null)
109             { 
110                 
111                 // take any available bytes from the parser buffer, which may have already been read
112                 Buffer buffer=_parser.getBuffer();
113                 if (buffer!=null && buffer.length()>0)
114                 {
115                     int l=buffer.length();
116                     if (l>(8-_hixieBytes.length()))
117                         l=8-_hixieBytes.length();
118                     _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
119                     buffer.skip(l);
120                 }
121                 
122                 // while we are not blocked
123                 while(_endp.isOpen())
124                 {
125                     // do we now have enough
126                     if (_hixieBytes.length()==8)
127                     {
128                         // we have the silly random bytes
129                         // so let's work out the stupid 16 byte reply.
130                         doTheHixieHixieShake();
131                         _endp.flush(_hixieBytes);
132                         _hixieBytes=null;
133                         _endp.flush();
134                         break;
135                     }
136 
137                     // no, then let's fill
138                     int filled=_endp.fill(_hixieBytes);
139                     if (filled<0)
140                     {
141                         _endp.close();
142                         break;
143                     }
144                 }
145 
146                 _websocket.onConnect(this);
147                 return this;
148             }
149             
150             // handle the framing protocol
151             boolean progress=true;
152 
153             while (progress)
154             {
155                 int flushed=_generator.flush();
156                 int filled=_parser.parseNext();
157 
158                 progress = flushed>0 || filled>0;
159 
160                 if (filled<0 || flushed<0)
161                 {
162                     _endp.close();
163                     break;
164                 }
165             }
166         }
167         catch(IOException e)
168         {
169             try
170             {
171                 _endp.close();
172             }
173             catch(IOException e2)
174             {
175                 Log.ignore(e2);
176             }
177             throw e;
178         }
179         finally
180         {
181             if (_endp.isOpen())
182             {
183                 _idle.access(_endp);
184                 checkWriteable();
185             }
186         }
187         return this;
188     }
189 
190     private void doTheHixieHixieShake()
191     {          
192         byte[] result=WebSocketConnection.doTheHixieHixieShake(
193                 WebSocketConnection.hixieCrypt(_key1),
194                 WebSocketConnection.hixieCrypt(_key2),
195                 _hixieBytes.asArray());
196         _hixieBytes.clear();
197         _hixieBytes.put(result);
198     }
199     
200     public boolean isOpen()
201     {
202         return _endp!=null&&_endp.isOpen();
203     }
204 
205     public boolean isIdle()
206     {
207         return _parser.isBufferEmpty() && _generator.isBufferEmpty();
208     }
209 
210     public boolean isSuspended()
211     {
212         return false;
213     }
214 
215     public void closed()
216     {
217         _websocket.onDisconnect();
218     }
219 
220     public long getTimeStamp()
221     {
222         return _timestamp;
223     }
224 
225     public void sendMessage(String content) throws IOException
226     {
227         sendMessage(WebSocket.SENTINEL_FRAME,content);
228     }
229 
230     public void sendMessage(byte frame, String content) throws IOException
231     {
232         _generator.addFrame(frame,content,_endp.getMaxIdleTime());
233         _generator.flush();
234         checkWriteable();
235         _idle.access(_endp);
236     }
237 
238     public void sendMessage(byte opcode, byte[] content, int offset, int length) throws IOException
239     {
240         _generator.addFrame(opcode,content,offset,length,_endp.getMaxIdleTime());
241         _generator.flush();
242         checkWriteable();
243         _idle.access(_endp);
244     }
245 
246     public void sendFragment(boolean more,byte opcode, byte[] content, int offset, int length) throws IOException
247     {
248         _generator.addFragment(more,opcode,content,offset,length,_endp.getMaxIdleTime());
249         _generator.flush();
250         checkWriteable();
251         _idle.access(_endp);
252     }
253 
254     public void disconnect()
255     {
256         try
257         {
258             _generator.flush(_endp.getMaxIdleTime());
259             _endp.close();
260         }
261         catch(IOException e)
262         {
263             Log.ignore(e);
264         }
265     }
266 
267     public void fill(Buffer buffer)
268     {
269         _parser.fill(buffer);
270     }
271 
272 
273     private void checkWriteable()
274     {
275         if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
276             ((AsyncEndPoint)_endp).scheduleWrite();
277     }
278 
279     /* ------------------------------------------------------------ */
280     static long hixieCrypt(String key)
281     {
282         // Don't ask me what all this is about.
283         // I think it's pretend secret stuff, kind of
284         // like talking in pig latin!
285         long number=0;
286         int spaces=0;
287         for (char c : key.toCharArray())
288         {
289             if (Character.isDigit(c))
290                 number=number*10+(c-'0');
291             else if (c==' ')
292                 spaces++;
293         }
294         return number/spaces;
295     }
296 
297     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
298     {            
299         try
300         {
301             MessageDigest md = MessageDigest.getInstance("MD5");
302             byte [] fodder = new byte[16];
303             
304             fodder[0]=(byte)(0xff&(key1>>24));
305             fodder[1]=(byte)(0xff&(key1>>16));
306             fodder[2]=(byte)(0xff&(key1>>8));
307             fodder[3]=(byte)(0xff&key1);
308             fodder[4]=(byte)(0xff&(key2>>24));
309             fodder[5]=(byte)(0xff&(key2>>16));
310             fodder[6]=(byte)(0xff&(key2>>8));
311             fodder[7]=(byte)(0xff&key2);
312             for (int i=0;i<8;i++)
313                 fodder[8+i]=key3[i];
314             md.update(fodder);
315             byte[] result=md.digest();
316             return result;
317         }
318         catch (NoSuchAlgorithmException e)
319         {
320             throw new IllegalStateException(e);
321         }
322     }
323 
324     private interface IdleCheck
325     {
326         void access(EndPoint endp);
327     }
328 }