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
33 import org.eclipse.jetty.io.Buffer;
34 import org.eclipse.jetty.io.Buffers;
35 import org.eclipse.jetty.io.EndPoint;
36 import org.eclipse.jetty.util.log.Log;
37 import org.eclipse.jetty.util.log.Logger;
38
39
40
41 /* ------------------------------------------------------------ */
42 /**
43 * Parser the WebSocket protocol.
44 *
45 */
46 public class WebSocketParserD08 implements WebSocketParser
47 {
48 private static final Logger LOG = Log.getLogger(WebSocketParserD08.class);
49
50 public enum State {
51
52 START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1);
53
54 int _needs;
55
56 State(int needs)
57 {
58 _needs=needs;
59 }
60
61 int getNeeds()
62 {
63 return _needs;
64 }
65 }
66
67 private final WebSocketBuffers _buffers;
68 private final EndPoint _endp;
69 private final FrameHandler _handler;
70 private final boolean _shouldBeMasked;
71 private State _state;
72 private Buffer _buffer;
73 private byte _flags;
74 private byte _opcode;
75 private int _bytesNeeded;
76 private long _length;
77 private boolean _masked;
78 private final byte[] _mask = new byte[4];
79 private int _m;
80 private boolean _skip;
81 private boolean _fakeFragments=true;
82
83 /* ------------------------------------------------------------ */
84 /**
85 * @param buffers The buffers to use for parsing. Only the {@link Buffers#getBuffer()} is used.
86 * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
87 * is mostly used.
88 * @param endp the endpoint
89 * @param handler the handler to notify when a parse event occurs
90 * @param shouldBeMasked whether masking should be handled
91 */
92 public WebSocketParserD08(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
93 {
94 _buffers=buffers;
95 _endp=endp;
96 _handler=handler;
97 _shouldBeMasked=shouldBeMasked;
98 _state=State.START;
99 }
100
101 /* ------------------------------------------------------------ */
102 /**
103 * @return True if fake fragments should be created for frames larger than the buffer.
104 */
105 public boolean isFakeFragments()
106 {
107 return _fakeFragments;
108 }
109
110 /* ------------------------------------------------------------ */
111 /**
112 * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
113 */
114 public void setFakeFragments(boolean fakeFragments)
115 {
116 _fakeFragments = fakeFragments;
117 }
118
119 /* ------------------------------------------------------------ */
120 public boolean isBufferEmpty()
121 {
122 return _buffer==null || _buffer.length()==0;
123 }
124
125 /* ------------------------------------------------------------ */
126 public Buffer getBuffer()
127 {
128 return _buffer;
129 }
130
131 /* ------------------------------------------------------------ */
132 /** Parse to next event.
133 * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
134 * available. Fill data from the {@link EndPoint} only as necessary.
135 * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
136 * that no bytes were read and no messages parsed. A positive number indicates either
137 * the bytes filled or the messages parsed.
138 */
139 public int parseNext()
140 {
141 if (_buffer==null)
142 _buffer=_buffers.getBuffer();
143 int total_filled=0;
144 int events=0;
145
146 // Loop until a datagram call back or can't fill anymore
147 while(true)
148 {
149 int available=_buffer.length();
150
151 // Fill buffer if we need a byte or need length
152 while (available<(_state==State.SKIP?1:_bytesNeeded))
153 {
154 // compact to mark (set at start of data)
155 _buffer.compact();
156
157 // if no space, then the data is too big for buffer
158 if (_buffer.space() == 0)
159 {
160 // Can we send a fake frame?
161 if (_fakeFragments && _state==State.DATA)
162 {
163 Buffer data =_buffer.get(4*(available/4));
164 _buffer.compact();
165 if (_masked)
166 {
167 if (data.array()==null)
168 data=_buffer.asMutableBuffer();
169 byte[] array = data.array();
170 final int end=data.putIndex();
171 for (int i=data.getIndex();i<end;i++)
172 array[i]^=_mask[_m++%4];
173 }
174
175 // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
176 events++;
177 _bytesNeeded-=data.length();
178 _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD08.FLAG_FIN)), _opcode, data);
179
180 _opcode=WebSocketConnectionD08.OP_CONTINUATION;
181 }
182
183 if (_buffer.space() == 0)
184 throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
185 }
186
187 // catch IOExceptions (probably EOF) and try to parse what we have
188 try
189 {
190 int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
191 if (filled<=0)
192 return (total_filled+events)>0?(total_filled+events):filled;
193 total_filled+=filled;
194 available=_buffer.length();
195 }
196 catch(IOException e)
197 {
198 LOG.debug(e);
199 return (total_filled+events)>0?(total_filled+events):-1;
200 }
201 }
202
203 // if we are here, then we have sufficient bytes to process the current state.
204
205 // Parse the buffer byte by byte (unless it is STATE_DATA)
206 byte b;
207 while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
208 {
209 switch (_state)
210 {
211 case START:
212 _skip=false;
213 _state=State.OPCODE;
214 _bytesNeeded=_state.getNeeds();
215 continue;
216
217 case OPCODE:
218 b=_buffer.get();
219 available--;
220 _opcode=(byte)(b&0xf);
221 _flags=(byte)(0xf&(b>>4));
222
223 if (WebSocketConnectionD08.isControlFrame(_opcode)&&!WebSocketConnectionD08.isLastFrame(_flags))
224 {
225 events++;
226 LOG.warn("Fragmented Control from "+_endp);
227 _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"Fragmented control");
228 _skip=true;
229 }
230
231 _state=State.LENGTH_7;
232 _bytesNeeded=_state.getNeeds();
233
234 continue;
235
236 case LENGTH_7:
237 b=_buffer.get();
238 available--;
239 _masked=(b&0x80)!=0;
240 b=(byte)(0x7f&b);
241
242 switch(b)
243 {
244 case 0x7f:
245 _length=0;
246 _state=State.LENGTH_63;
247 break;
248 case 0x7e:
249 _length=0;
250 _state=State.LENGTH_16;
251 break;
252 default:
253 _length=(0x7f&b);
254 _state=_masked?State.MASK:State.PAYLOAD;
255 }
256 _bytesNeeded=_state.getNeeds();
257 continue;
258
259 case LENGTH_16:
260 b=_buffer.get();
261 available--;
262 _length = _length*0x100 + (0xff&b);
263 if (--_bytesNeeded==0)
264 {
265 if (_length>_buffer.capacity() && !_fakeFragments)
266 {
267 events++;
268 _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
269 _skip=true;
270 }
271
272 _state=_masked?State.MASK:State.PAYLOAD;
273 _bytesNeeded=_state.getNeeds();
274 }
275 continue;
276
277 case LENGTH_63:
278 b=_buffer.get();
279 available--;
280 _length = _length*0x100 + (0xff&b);
281 if (--_bytesNeeded==0)
282 {
283 _bytesNeeded=(int)_length;
284 if (_length>=_buffer.capacity() && !_fakeFragments)
285 {
286 events++;
287 _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
288 _skip=true;
289 }
290
291 _state=_masked?State.MASK:State.PAYLOAD;
292 _bytesNeeded=_state.getNeeds();
293 }
294 continue;
295
296 case MASK:
297 _buffer.get(_mask,0,4);
298 _m=0;
299 available-=4;
300 _state=State.PAYLOAD;
301 _bytesNeeded=_state.getNeeds();
302 break;
303
304 case PAYLOAD:
305 _bytesNeeded=(int)_length;
306 _state=_skip?State.SKIP:State.DATA;
307 break;
308
309 case DATA:
310 break;
311
312 case SKIP:
313 int skip=Math.min(available,_bytesNeeded);
314 _buffer.skip(skip);
315 available-=skip;
316 _bytesNeeded-=skip;
317 if (_bytesNeeded==0)
318 _state=State.START;
319
320 }
321 }
322
323 if (_state==State.DATA && available>=_bytesNeeded)
324 {
325 if ( _masked!=_shouldBeMasked)
326 {
327 _buffer.skip(_bytesNeeded);
328 _state=State.START;
329 events++;
330 _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"bad mask");
331 }
332 else
333 {
334 Buffer data =_buffer.get(_bytesNeeded);
335 if (_masked)
336 {
337 if (data.array()==null)
338 data=_buffer.asMutableBuffer();
339 byte[] array = data.array();
340 final int end=data.putIndex();
341 for (int i=data.getIndex();i<end;i++)
342 array[i]^=_mask[_m++%4];
343 }
344
345 // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
346 events++;
347 _handler.onFrame(_flags, _opcode, data);
348 _bytesNeeded=0;
349 _state=State.START;
350 }
351
352 return total_filled+events;
353 }
354 }
355 }
356
357 /* ------------------------------------------------------------ */
358 public void fill(Buffer buffer)
359 {
360 if (buffer!=null && buffer.length()>0)
361 {
362 if (_buffer==null)
363 _buffer=_buffers.getBuffer();
364
365 _buffer.put(buffer);
366 buffer.clear();
367 }
368 }
369
370 /* ------------------------------------------------------------ */
371 public void returnBuffer()
372 {
373 if (_buffer!=null && _buffer.length()==0)
374 {
375 _buffers.returnBuffer(_buffer);
376 _buffer=null;
377 }
378 }
379
380 /* ------------------------------------------------------------ */
381 @Override
382 public String toString()
383 {
384 Buffer buffer=_buffer;
385 return WebSocketParserD08.class.getSimpleName()+"@"+ Integer.toHexString(hashCode())+"|"+_state+"|"+(buffer==null?"<>":buffer.toDetailString());
386 }
387
388 }