View Javadoc

1   package org.eclipse.jetty.websocket;
2   
3   import java.io.IOException;
4   import java.math.BigInteger;
5   import java.security.MessageDigest;
6   import java.security.NoSuchAlgorithmException;
7   
8   import org.eclipse.jetty.io.Buffer;
9   import org.eclipse.jetty.io.EndPoint;
10  import org.eclipse.jetty.io.EofException;
11  import org.eclipse.jetty.util.TypeUtil;
12  import org.eclipse.jetty.util.log.Log;
13  
14  
15  /* ------------------------------------------------------------ */
16  /** WebSocketGenerator.
17   * This class generates websocket packets.
18   * It is fully synchronized because it is likely that async
19   * threads will call the addMessage methods while other
20   * threads are flushing the generator.
21   */
22  public class WebSocketGenerator
23  {
24      final private WebSocketBuffers _buffers;
25      final private EndPoint _endp;
26      private Buffer _buffer;
27  
28      public WebSocketGenerator(WebSocketBuffers buffers, EndPoint endp)
29      {
30          _buffers=buffers;
31          _endp=endp;
32      }
33  
34      public synchronized void addFrame(byte frame,byte[] content, int blockFor) throws IOException
35      {
36          addFrame(frame,content,0,content.length,blockFor);
37      }
38  
39      public synchronized void addFrame(byte frame,byte[] content, int offset, int length, int blockFor) throws IOException
40      {
41          if (_buffer==null)
42              _buffer=_buffers.getDirectBuffer();
43  
44          if (_buffer.space() == 0)
45              expelBuffer(blockFor);
46  
47          bufferPut(frame, blockFor);
48  
49          if (isLengthFrame(frame))
50          {
51              // Send a length delimited frame
52  
53              // How many bytes we need for the length ?
54              // We have 7 bits available, so log2(length) / 7 + 1
55              // For example, 50000 bytes is 2 8-bytes: 11000011 01010000
56              // but we need to write it in 3 7-bytes 0000011 0000110 1010000
57              // 65536 == 1 00000000 00000000 => 100 0000000 0000000
58              int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
59              for (int i = lengthBytes - 1; i > 0; --i)
60              {
61                  byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
62                  bufferPut(lengthByte, blockFor);
63              }
64              bufferPut((byte)(0x7F & length), blockFor);
65          }
66  
67          int remaining = length;
68          while (remaining > 0)
69          {
70              int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
71              _buffer.put(content, offset + (length - remaining), chunk);
72              remaining -= chunk;
73              if (_buffer.space() > 0)
74              {
75                  if (!isLengthFrame(frame))
76                      _buffer.put((byte)0xFF);
77                  // Gently flush the data, issuing a non-blocking write
78                  flushBuffer();
79              }
80              else
81              {
82                  // Forcibly flush the data, issuing a blocking write
83                  expelBuffer(blockFor);
84                  if (remaining == 0)
85                  {
86                      if (!isLengthFrame(frame))
87                          _buffer.put((byte)0xFF);
88                      // Gently flush the data, issuing a non-blocking write
89                      flushBuffer();
90                  }
91              }
92          }
93      }
94  
95      private synchronized boolean isLengthFrame(byte frame)
96      {
97          return (frame & WebSocket.LENGTH_FRAME) == WebSocket.LENGTH_FRAME;
98      }
99  
100     private synchronized void bufferPut(byte datum, long blockFor) throws IOException
101     {
102         if (_buffer==null)
103             _buffer=_buffers.getDirectBuffer();
104         _buffer.put(datum);
105         if (_buffer.space() == 0)
106             expelBuffer(blockFor);
107     }
108 
109     public synchronized void addFrame(byte frame, String content, int blockFor) throws IOException
110     {
111         byte[] bytes = content.getBytes("UTF-8");
112         addFrame(frame, bytes, 0, bytes.length, blockFor);
113     }
114 
115     public synchronized int flush(long blockFor) throws IOException
116     {
117         return expelBuffer(blockFor);
118     }
119 
120     public synchronized int flush() throws IOException
121     {
122         int flushed = flushBuffer();
123         if (_buffer!=null && _buffer.length()==0)
124         {
125             _buffers.returnBuffer(_buffer);
126             _buffer=null;
127         }
128         return flushed;
129     }
130 
131     private synchronized int flushBuffer() throws IOException
132     {
133         if (!_endp.isOpen())
134             throw new EofException();
135 
136         if (_buffer!=null)
137             return _endp.flush(_buffer);
138 
139         return 0;
140     }
141 
142     private synchronized int expelBuffer(long blockFor) throws IOException
143     {
144         if (_buffer==null)
145             return 0;
146         int result = flushBuffer();
147         _buffer.compact();
148         if (!_endp.isBlocking())
149         {
150             while (_buffer.space()==0)
151             {
152                 // TODO: in case the I/O system signals write ready, but when we attempt to write we cannot
153                 // TODO: we should decrease the blockFor timeout instead of waiting again the whole timeout
154                 boolean ready = _endp.blockWritable(blockFor);
155                 if (!ready)
156                     throw new IOException("Write timeout");
157 
158                 result += flushBuffer();
159                 _buffer.compact();
160             }
161         }
162         return result;
163     }
164 
165     public synchronized boolean isBufferEmpty()
166     {
167         return _buffer==null || _buffer.length()==0;
168     }
169 
170     public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
171     {            
172         try
173         {
174             MessageDigest md = MessageDigest.getInstance("MD5");
175             byte [] fodder = new byte[16];
176             
177             fodder[0]=(byte)(0xff&(key1>>24));
178             fodder[1]=(byte)(0xff&(key1>>16));
179             fodder[2]=(byte)(0xff&(key1>>8));
180             fodder[3]=(byte)(0xff&key1);
181             fodder[4]=(byte)(0xff&(key2>>24));
182             fodder[5]=(byte)(0xff&(key2>>16));
183             fodder[6]=(byte)(0xff&(key2>>8));
184             fodder[7]=(byte)(0xff&key2);
185             for (int i=0;i<8;i++)
186                 fodder[8+i]=key3[i];
187             md.update(fodder);
188             byte[] result=md.digest();
189             return result;
190         }
191         catch (NoSuchAlgorithmException e)
192         {
193             throw new IllegalStateException(e);
194         }
195     }
196     
197 }