1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 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
19package org.eclipse.jetty.websocket;
20
21import java.io.IOException;
22
23import org.eclipse.jetty.io.Buffer;
24import org.eclipse.jetty.io.Buffers;
25import org.eclipse.jetty.io.EndPoint;
26import org.eclipse.jetty.util.TypeUtil;
27import org.eclipse.jetty.util.log.Log;
28import org.eclipse.jetty.util.log.Logger;
29
30
31
32
33/* ------------------------------------------------------------ */
34/**
35 * Parser the WebSocket protocol.
36 *
37 */
38public class WebSocketParserD00 implements WebSocketParser
39{
40    private static final Logger LOG = Log.getLogger(WebSocketParserD00.class);
41
42    public static final int STATE_START=0;
43    public static final int STATE_SENTINEL_DATA=1;
44    public static final int STATE_LENGTH=2;
45    public static final int STATE_DATA=3;
46
47    private final WebSocketBuffers _buffers;
48    private final EndPoint _endp;
49    private final FrameHandler _handler;
50    private int _state;
51    private Buffer _buffer;
52    private byte _opcode;
53    private int _length;
54
55    /* ------------------------------------------------------------ */
56    /**
57     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
58     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
59     * is mostly used.
60     * @param endp the endpoint
61     * @param handler the handler to notify when a parse event occurs
62     */
63    public WebSocketParserD00(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler)
64    {
65        _buffers=buffers;
66        _endp=endp;
67        _handler=handler;
68    }
69
70    /* ------------------------------------------------------------ */
71    public boolean isBufferEmpty()
72    {
73        return _buffer==null || _buffer.length()==0;
74    }
75
76    /* ------------------------------------------------------------ */
77    public Buffer getBuffer()
78    {
79        return _buffer;
80    }
81
82    /* ------------------------------------------------------------ */
83    /** Parse to next event.
84     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
85     * available. Fill data from the {@link EndPoint} only as necessary.
86     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
87     * that no bytes were read and no messages parsed. A positive number indicates either
88     * the bytes filled or the messages parsed.
89     */
90    public int parseNext()
91    {
92        if (_buffer==null)
93            _buffer=_buffers.getBuffer();
94
95        int progress=0;
96
97        // Loop until an datagram call back or can't fill anymore
98        while(true)
99        {
100            int length=_buffer.length();
101
102            // Fill buffer if we need a byte or need length
103            if (length == 0 || _state==STATE_DATA && length<_length)
104            {
105                // compact to mark (set at start of data)
106                _buffer.compact();
107
108                // if no space, then the data is too big for buffer
109                if (_buffer.space() == 0)
110                    throw new IllegalStateException("FULL");
111
112                // catch IOExceptions (probably EOF) and try to parse what we have
113                try
114                {
115                    int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
116                    if (filled<=0)
117                        return progress;
118                    progress+=filled;
119                    length=_buffer.length();
120                }
121                catch(IOException e)
122                {
123                    LOG.debug(e);
124                    return progress>0?progress:-1;
125                }
126            }
127
128
129            // Parse the buffer byte by byte (unless it is STATE_DATA)
130            byte b;
131            charloop: while (length-->0)
132            {
133                switch (_state)
134                {
135                    case STATE_START:
136                        b=_buffer.get();
137                        _opcode=b;
138                        if (_opcode<0)
139                        {
140                            _length=0;
141                            _state=STATE_LENGTH;
142                        }
143                        else
144                        {
145                            _state=STATE_SENTINEL_DATA;
146                            _buffer.mark(0);
147                        }
148                        continue;
149
150                    case STATE_SENTINEL_DATA:
151                        b=_buffer.get();
152                        if ((b&0xff)==0xff)
153                        {
154                            _state=STATE_START;
155                            int l=_buffer.getIndex()-_buffer.markIndex()-1;
156                            progress++;
157                            _handler.onFrame((byte)0,_opcode,_buffer.sliceFromMark(l));
158                            _buffer.setMarkIndex(-1);
159                            if (_buffer.length()==0)
160                            {
161                                _buffers.returnBuffer(_buffer);
162                                _buffer=null;
163                            }
164                            return progress;
165                        }
166                        continue;
167
168                    case STATE_LENGTH:
169                        b=_buffer.get();
170                        _length=_length<<7 | (0x7f&b);
171                        if (b>=0)
172                        {
173                            _state=STATE_DATA;
174                            _buffer.mark(0);
175                        }
176                        continue;
177
178                    case STATE_DATA:
179                        if (_buffer.markIndex()<0)
180                        if (_buffer.length()<_length)
181                            break charloop;
182                        Buffer data=_buffer.sliceFromMark(_length);
183                        _buffer.skip(_length);
184                        _state=STATE_START;
185                        progress++;
186                        _handler.onFrame((byte)0, _opcode, data);
187
188                        if (_buffer.length()==0)
189                        {
190                            _buffers.returnBuffer(_buffer);
191                            _buffer=null;
192                        }
193
194                        return progress;
195                }
196            }
197        }
198    }
199
200    /* ------------------------------------------------------------ */
201    public void fill(Buffer buffer)
202    {
203        if (buffer!=null && buffer.length()>0)
204        {
205            if (_buffer==null)
206                _buffer=_buffers.getBuffer();
207            _buffer.put(buffer);
208            buffer.clear();
209        }
210    }
211
212}
213