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 WebSocketParserRFC6455 implements WebSocketParser
47 {
48 private static final Logger LOG = Log.getLogger(WebSocketParserRFC6455.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), SEEK_EOF(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 _fragmentFrames=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 WebSocketParserRFC6455(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 _fragmentFrames;
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 _fragmentFrames = 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
144 boolean progress=false;
145 int filled=-1;
146
147 // Loop until a datagram call back or can't fill anymore
148 while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
149 {
150 int available=_buffer.length();
151
152 // Fill buffer if we need a byte or need length
153 while (available<(_state==State.SKIP?1:_bytesNeeded))
154 {
155 // compact to mark (set at start of data)
156 _buffer.compact();
157
158 // if no space, then the data is too big for buffer
159 if (_buffer.space() == 0)
160 {
161 // Can we send a fake frame?
162 if (_fragmentFrames && _state==State.DATA)
163 {
164 Buffer data =_buffer.get(4*(available/4));
165 _buffer.compact();
166 if (_masked)
167 {
168 if (data.array()==null)
169 data=_buffer.asMutableBuffer();
170 byte[] array = data.array();
171 final int end=data.putIndex();
172 for (int i=data.getIndex();i<end;i++)
173 array[i]^=_mask[_m++%4];
174 }
175
176 // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
177 _bytesNeeded-=data.length();
178 progress=true;
179 _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data);
180
181 _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION;
182 }
183
184 if (_buffer.space() == 0)
185 throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
186 }
187
188 // catch IOExceptions (probably EOF) and try to parse what we have
189 try
190 {
191 filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
192 available=_buffer.length();
193 // System.err.printf(">> filled %d/%d%n",filled,available);
194 if (filled<=0)
195 break;
196 }
197 catch(IOException e)
198 {
199 LOG.debug(e);
200 filled=-1;
201 break;
202 }
203 }
204 // Did we get enough?
205 if (available<(_state==State.SKIP?1:_bytesNeeded))
206 break;
207
208 // if we are here, then we have sufficient bytes to process the current state.
209 // Parse the buffer byte by byte (unless it is STATE_DATA)
210 byte b;
211 while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
212 {
213 switch (_state)
214 {
215 case START:
216 _skip=false;
217 _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
218 _bytesNeeded=_state.getNeeds();
219 continue;
220
221 case OPCODE:
222 b=_buffer.get();
223 available--;
224 _opcode=(byte)(b&0xf);
225 _flags=(byte)(0xf&(b>>4));
226
227 if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags))
228 {
229 LOG.warn("Fragmented Control from "+_endp);
230 _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control");
231 progress=true;
232 _skip=true;
233 }
234
235 _state=State.LENGTH_7;
236 _bytesNeeded=_state.getNeeds();
237
238 continue;
239
240 case LENGTH_7:
241 b=_buffer.get();
242 available--;
243 _masked=(b&0x80)!=0;
244 b=(byte)(0x7f&b);
245
246 switch(b)
247 {
248 case 0x7f:
249 _length=0;
250 _state=State.LENGTH_63;
251 break;
252 case 0x7e:
253 _length=0;
254 _state=State.LENGTH_16;
255 break;
256 default:
257 _length=(0x7f&b);
258 _state=_masked?State.MASK:State.PAYLOAD;
259 }
260 _bytesNeeded=_state.getNeeds();
261 continue;
262
263 case LENGTH_16:
264 b=_buffer.get();
265 available--;
266 _length = _length*0x100 + (0xff&b);
267 if (--_bytesNeeded==0)
268 {
269 if (_length>_buffer.capacity() && !_fragmentFrames)
270 {
271 progress=true;
272 _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
273 _skip=true;
274 }
275
276 _state=_masked?State.MASK:State.PAYLOAD;
277 _bytesNeeded=_state.getNeeds();
278 }
279 continue;
280
281 case LENGTH_63:
282 b=_buffer.get();
283 available--;
284 _length = _length*0x100 + (0xff&b);
285 if (--_bytesNeeded==0)
286 {
287 _bytesNeeded=(int)_length;
288 if (_length>=_buffer.capacity() && !_fragmentFrames)
289 {
290 progress=true;
291 _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
292 _skip=true;
293 }
294
295 _state=_masked?State.MASK:State.PAYLOAD;
296 _bytesNeeded=_state.getNeeds();
297 }
298 continue;
299
300 case MASK:
301 _buffer.get(_mask,0,4);
302 _m=0;
303 available-=4;
304 _state=State.PAYLOAD;
305 _bytesNeeded=_state.getNeeds();
306 break;
307
308 case PAYLOAD:
309 _bytesNeeded=(int)_length;
310 _state=_skip?State.SKIP:State.DATA;
311 break;
312
313 case DATA:
314 break;
315
316 case SKIP:
317 int skip=Math.min(available,_bytesNeeded);
318 progress=true;
319 _buffer.skip(skip);
320 available-=skip;
321 _bytesNeeded-=skip;
322 if (_bytesNeeded==0)
323 _state=State.START;
324 break;
325
326 case SEEK_EOF:
327 progress=true;
328 _buffer.skip(available);
329 available=0;
330 break;
331 }
332 }
333
334 if (_state==State.DATA && available>=_bytesNeeded)
335 {
336 if ( _masked!=_shouldBeMasked)
337 {
338 _buffer.skip(_bytesNeeded);
339 _state=State.START;
340 progress=true;
341 _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked");
342 }
343 else
344 {
345 Buffer data =_buffer.get(_bytesNeeded);
346 if (_masked)
347 {
348 if (data.array()==null)
349 data=_buffer.asMutableBuffer();
350 byte[] array = data.array();
351 final int end=data.putIndex();
352 for (int i=data.getIndex();i<end;i++)
353 array[i]^=_mask[_m++%4];
354 }
355
356 // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
357
358 progress=true;
359 _handler.onFrame(_flags, _opcode, data);
360 _bytesNeeded=0;
361 _state=State.START;
362 }
363
364 break;
365 }
366 }
367
368 return progress?1:filled;
369 }
370
371 /* ------------------------------------------------------------ */
372 public void fill(Buffer buffer)
373 {
374 if (buffer!=null && buffer.length()>0)
375 {
376 if (_buffer==null)
377 _buffer=_buffers.getBuffer();
378
379 _buffer.put(buffer);
380 buffer.clear();
381 }
382 }
383
384 /* ------------------------------------------------------------ */
385 public void returnBuffer()
386 {
387 if (_buffer!=null && _buffer.length()==0)
388 {
389 _buffers.returnBuffer(_buffer);
390 _buffer=null;
391 }
392 }
393
394 /* ------------------------------------------------------------ */
395 @Override
396 public String toString()
397 {
398 return String.format("%s@%x state=%s buffer=%s",
399 getClass().getSimpleName(),
400 hashCode(),
401 _state,
402 _buffer);
403 }
404 }