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;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.Collections;
25import java.util.List;
26
27import org.eclipse.jetty.io.AbstractConnection;
28import org.eclipse.jetty.io.AsyncEndPoint;
29import org.eclipse.jetty.io.Buffer;
30import org.eclipse.jetty.io.ByteArrayBuffer;
31import org.eclipse.jetty.io.Connection;
32import org.eclipse.jetty.io.EndPoint;
33import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
34import org.eclipse.jetty.util.StringUtil;
35import org.eclipse.jetty.util.log.Log;
36import org.eclipse.jetty.util.log.Logger;
37import org.eclipse.jetty.websocket.WebSocket.OnFrame;
38
39public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
40{
41    private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
42
43    public final static byte LENGTH_FRAME=(byte)0x80;
44    public final static byte SENTINEL_FRAME=(byte)0x00;
45
46    private final WebSocketParser _parser;
47    private final WebSocketGenerator _generator;
48    private final WebSocket _websocket;
49    private final String _protocol;
50    private String _key1;
51    private String _key2;
52    private ByteArrayBuffer _hixieBytes;
53
54    public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
55        throws IOException
56    {
57        super(endpoint,timestamp);
58
59        _endp.setMaxIdleTime(maxIdleTime);
60
61        _websocket = websocket;
62        _protocol=protocol;
63
64        _generator = new WebSocketGeneratorD00(buffers, _endp);
65        _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
66    }
67
68    /* ------------------------------------------------------------ */
69    public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
70    {
71        return this;
72    }
73
74
75    /* ------------------------------------------------------------ */
76    public void setHixieKeys(String key1,String key2)
77    {
78        _key1=key1;
79        _key2=key2;
80        _hixieBytes=new IndirectNIOBuffer(16);
81    }
82
83    /* ------------------------------------------------------------ */
84    public Connection handle() throws IOException
85    {
86        try
87        {
88            // handle stupid hixie random bytes
89            if (_hixieBytes!=null)
90            {
91
92                // take any available bytes from the parser buffer, which may have already been read
93                Buffer buffer=_parser.getBuffer();
94                if (buffer!=null && buffer.length()>0)
95                {
96                    int l=buffer.length();
97                    if (l>(8-_hixieBytes.length()))
98                        l=8-_hixieBytes.length();
99                    _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
100                    buffer.skip(l);
101                }
102
103                // while we are not blocked
104                while(_endp.isOpen())
105                {
106                    // do we now have enough
107                    if (_hixieBytes.length()==8)
108                    {
109                        // we have the silly random bytes
110                        // so let's work out the stupid 16 byte reply.
111                        doTheHixieHixieShake();
112                        _endp.flush(_hixieBytes);
113                        _hixieBytes=null;
114                        _endp.flush();
115                        break;
116                    }
117
118                    // no, then let's fill
119                    int filled=_endp.fill(_hixieBytes);
120                    if (filled<0)
121                    {
122                        _endp.flush();
123                        _endp.close();
124                        break;
125                    }
126                    else if (filled==0)
127                        return this;
128                }
129
130                if (_websocket instanceof OnFrame)
131                    ((OnFrame)_websocket).onHandshake(this);
132                _websocket.onOpen(this);
133                return this;
134            }
135
136            // handle the framing protocol
137            boolean progress=true;
138
139            while (progress)
140            {
141                int flushed=_generator.flush();
142                int filled=_parser.parseNext();
143
144                progress = flushed>0 || filled>0;
145
146                _endp.flush();
147
148                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
149                    progress=true;
150            }
151        }
152        catch(IOException e)
153        {
154            LOG.debug(e);
155            try
156            {
157                if (_endp.isOpen())
158                    _endp.close();
159            }
160            catch(IOException e2)
161            {
162                LOG.ignore(e2);
163            }
164            throw e;
165        }
166        finally
167        {
168            if (_endp.isOpen())
169            {
170                if (_endp.isInputShutdown() && _generator.isBufferEmpty())
171                    _endp.close();
172                else
173                    checkWriteable();
174
175                checkWriteable();
176            }
177        }
178        return this;
179    }
180
181    /* ------------------------------------------------------------ */
182    public void onInputShutdown() throws IOException
183    {
184        // TODO
185    }
186
187    /* ------------------------------------------------------------ */
188    private void doTheHixieHixieShake()
189    {
190        byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
191                WebSocketConnectionD00.hixieCrypt(_key1),
192                WebSocketConnectionD00.hixieCrypt(_key2),
193                _hixieBytes.asArray());
194        _hixieBytes.clear();
195        _hixieBytes.put(result);
196    }
197
198    /* ------------------------------------------------------------ */
199    public boolean isOpen()
200    {
201        return _endp!=null&&_endp.isOpen();
202    }
203
204    /* ------------------------------------------------------------ */
205    public boolean isIdle()
206    {
207        return _parser.isBufferEmpty() && _generator.isBufferEmpty();
208    }
209
210    /* ------------------------------------------------------------ */
211    public boolean isSuspended()
212    {
213        return false;
214    }
215
216    /* ------------------------------------------------------------ */
217    public void onClose()
218    {
219        _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
220    }
221
222    /* ------------------------------------------------------------ */
223    /**
224     */
225    public void sendMessage(String content) throws IOException
226    {
227        byte[] data = content.getBytes(StringUtil.__UTF8);
228        _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
229        _generator.flush();
230        checkWriteable();
231    }
232
233    /* ------------------------------------------------------------ */
234    public void sendMessage(byte[] data, int offset, int length) throws IOException
235    {
236        _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
237        _generator.flush();
238        checkWriteable();
239    }
240
241    /* ------------------------------------------------------------ */
242    public boolean isMore(byte flags)
243    {
244        return (flags&0x8) != 0;
245    }
246
247    /* ------------------------------------------------------------ */
248    /**
249     * {@inheritDoc}
250     */
251    public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
252    {
253    }
254
255    /* ------------------------------------------------------------ */
256    public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
257    {
258        _generator.addFrame((byte)0,opcode,content,offset,length);
259        _generator.flush();
260        checkWriteable();
261    }
262
263    /* ------------------------------------------------------------ */
264    public void close(int code, String message)
265    {
266        throw new UnsupportedOperationException();
267    }
268
269    /* ------------------------------------------------------------ */
270    public void disconnect()
271    {
272        close();
273    }
274
275    /* ------------------------------------------------------------ */
276    public void close()
277    {
278        try
279        {
280            _generator.flush();
281            _endp.close();
282        }
283        catch(IOException e)
284        {
285            LOG.ignore(e);
286        }
287    }
288
289    public void shutdown()
290    {
291        close();
292    }
293
294    /* ------------------------------------------------------------ */
295    public void fillBuffersFrom(Buffer buffer)
296    {
297        _parser.fill(buffer);
298    }
299
300
301    /* ------------------------------------------------------------ */
302    private void checkWriteable()
303    {
304        if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
305            ((AsyncEndPoint)_endp).scheduleWrite();
306    }
307
308    /* ------------------------------------------------------------ */
309    static long hixieCrypt(String key)
310    {
311        // Don't ask me what all this is about.
312        // I think it's pretend secret stuff, kind of
313        // like talking in pig latin!
314        long number=0;
315        int spaces=0;
316        for (char c : key.toCharArray())
317        {
318            if (Character.isDigit(c))
319                number=number*10+(c-'0');
320            else if (c==' ')
321                spaces++;
322        }
323        return number/spaces;
324    }
325
326    public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
327    {
328        try
329        {
330            MessageDigest md = MessageDigest.getInstance("MD5");
331            byte [] fodder = new byte[16];
332
333            fodder[0]=(byte)(0xff&(key1>>24));
334            fodder[1]=(byte)(0xff&(key1>>16));
335            fodder[2]=(byte)(0xff&(key1>>8));
336            fodder[3]=(byte)(0xff&key1);
337            fodder[4]=(byte)(0xff&(key2>>24));
338            fodder[5]=(byte)(0xff&(key2>>16));
339            fodder[6]=(byte)(0xff&(key2>>8));
340            fodder[7]=(byte)(0xff&key2);
341            System.arraycopy(key3, 0, fodder, 8, 8);
342            md.update(fodder);
343            return md.digest();
344        }
345        catch (NoSuchAlgorithmException e)
346        {
347            throw new IllegalStateException(e);
348        }
349    }
350
351    public void setMaxTextMessageSize(int size)
352    {
353    }
354
355    public void setMaxIdleTime(int ms)
356    {
357        try
358        {
359            _endp.setMaxIdleTime(ms);
360        }
361        catch(IOException e)
362        {
363            LOG.warn(e);
364        }
365    }
366
367    public void setMaxBinaryMessageSize(int size)
368    {
369    }
370
371    public int getMaxTextMessageSize()
372    {
373        return -1;
374    }
375
376    public int getMaxIdleTime()
377    {
378        return _endp.getMaxIdleTime();
379    }
380
381    public int getMaxBinaryMessageSize()
382    {
383        return -1;
384    }
385
386    public String getProtocol()
387    {
388        return _protocol;
389    }
390
391    protected void onFrameHandshake()
392    {
393        if (_websocket instanceof OnFrame)
394        {
395            ((OnFrame)_websocket).onHandshake(this);
396        }
397    }
398
399    protected void onWebsocketOpen()
400    {
401        _websocket.onOpen(this);
402    }
403
404    static class FrameHandlerD00 implements WebSocketParser.FrameHandler
405    {
406        final WebSocket _websocket;
407
408        FrameHandlerD00(WebSocket websocket)
409        {
410            _websocket=websocket;
411        }
412
413        public void onFrame(byte flags, byte opcode, Buffer buffer)
414        {
415            try
416            {
417                byte[] array=buffer.array();
418
419                if (opcode==0)
420                {
421                    if (_websocket instanceof WebSocket.OnTextMessage)
422                        ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
423                }
424                else
425                {
426                    if (_websocket instanceof WebSocket.OnBinaryMessage)
427                        ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
428                }
429            }
430            catch(Throwable th)
431            {
432                LOG.warn(th);
433            }
434        }
435
436        public void close(int code,String message)
437        {
438        }
439    }
440
441    public boolean isMessageComplete(byte flags)
442    {
443        return true;
444    }
445
446    public byte binaryOpcode()
447    {
448        return LENGTH_FRAME;
449    }
450
451    public byte textOpcode()
452    {
453        return SENTINEL_FRAME;
454    }
455
456    public boolean isControl(byte opcode)
457    {
458        return false;
459    }
460
461    public boolean isText(byte opcode)
462    {
463        return (opcode&LENGTH_FRAME)==0;
464    }
465
466    public boolean isBinary(byte opcode)
467    {
468        return (opcode&LENGTH_FRAME)!=0;
469    }
470
471    public boolean isContinuation(byte opcode)
472    {
473        return false;
474    }
475
476    public boolean isClose(byte opcode)
477    {
478        return false;
479    }
480
481    public boolean isPing(byte opcode)
482    {
483        return false;
484    }
485
486    public boolean isPong(byte opcode)
487    {
488        return false;
489    }
490
491    public List<Extension> getExtensions()
492    {
493        return Collections.emptyList();
494    }
495
496    public byte continuationOpcode()
497    {
498        return 0;
499    }
500
501    public byte finMask()
502    {
503        return 0;
504    }
505
506    public void setAllowFrameFragmentation(boolean allowFragmentation)
507    {
508    }
509
510    public boolean isAllowFrameFragmentation()
511    {
512        return false;
513    }
514}
515