| // |
| // ======================================================================== |
| // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.websocket; |
| |
| import java.io.IOException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.jetty.io.AbstractConnection; |
| import org.eclipse.jetty.io.AsyncEndPoint; |
| import org.eclipse.jetty.io.Buffer; |
| import org.eclipse.jetty.io.ByteArrayBuffer; |
| import org.eclipse.jetty.io.Connection; |
| import org.eclipse.jetty.io.EndPoint; |
| import org.eclipse.jetty.io.nio.IndirectNIOBuffer; |
| import org.eclipse.jetty.util.StringUtil; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| import org.eclipse.jetty.websocket.WebSocket.OnFrame; |
| |
| public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection |
| { |
| private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class); |
| |
| public final static byte LENGTH_FRAME=(byte)0x80; |
| public final static byte SENTINEL_FRAME=(byte)0x00; |
| |
| private final WebSocketParser _parser; |
| private final WebSocketGenerator _generator; |
| private final WebSocket _websocket; |
| private final String _protocol; |
| private String _key1; |
| private String _key2; |
| private ByteArrayBuffer _hixieBytes; |
| |
| public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol) |
| throws IOException |
| { |
| super(endpoint,timestamp); |
| |
| _endp.setMaxIdleTime(maxIdleTime); |
| |
| _websocket = websocket; |
| _protocol=protocol; |
| |
| _generator = new WebSocketGeneratorD00(buffers, _endp); |
| _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public org.eclipse.jetty.websocket.WebSocket.Connection getConnection() |
| { |
| return this; |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| public void setHixieKeys(String key1,String key2) |
| { |
| _key1=key1; |
| _key2=key2; |
| _hixieBytes=new IndirectNIOBuffer(16); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public Connection handle() throws IOException |
| { |
| try |
| { |
| // handle stupid hixie random bytes |
| if (_hixieBytes!=null) |
| { |
| |
| // take any available bytes from the parser buffer, which may have already been read |
| Buffer buffer=_parser.getBuffer(); |
| if (buffer!=null && buffer.length()>0) |
| { |
| int l=buffer.length(); |
| if (l>(8-_hixieBytes.length())) |
| l=8-_hixieBytes.length(); |
| _hixieBytes.put(buffer.peek(buffer.getIndex(),l)); |
| buffer.skip(l); |
| } |
| |
| // while we are not blocked |
| while(_endp.isOpen()) |
| { |
| // do we now have enough |
| if (_hixieBytes.length()==8) |
| { |
| // we have the silly random bytes |
| // so let's work out the stupid 16 byte reply. |
| doTheHixieHixieShake(); |
| _endp.flush(_hixieBytes); |
| _hixieBytes=null; |
| _endp.flush(); |
| break; |
| } |
| |
| // no, then let's fill |
| int filled=_endp.fill(_hixieBytes); |
| if (filled<0) |
| { |
| _endp.flush(); |
| _endp.close(); |
| break; |
| } |
| else if (filled==0) |
| return this; |
| } |
| |
| if (_websocket instanceof OnFrame) |
| ((OnFrame)_websocket).onHandshake(this); |
| _websocket.onOpen(this); |
| return this; |
| } |
| |
| // handle the framing protocol |
| boolean progress=true; |
| |
| while (progress) |
| { |
| int flushed=_generator.flush(); |
| int filled=_parser.parseNext(); |
| |
| progress = flushed>0 || filled>0; |
| |
| _endp.flush(); |
| |
| if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed()) |
| progress=true; |
| } |
| } |
| catch(IOException e) |
| { |
| LOG.debug(e); |
| try |
| { |
| if (_endp.isOpen()) |
| _endp.close(); |
| } |
| catch(IOException e2) |
| { |
| LOG.ignore(e2); |
| } |
| throw e; |
| } |
| finally |
| { |
| if (_endp.isOpen()) |
| { |
| if (_endp.isInputShutdown() && _generator.isBufferEmpty()) |
| _endp.close(); |
| else |
| checkWriteable(); |
| |
| checkWriteable(); |
| } |
| } |
| return this; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void onInputShutdown() throws IOException |
| { |
| // TODO |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private void doTheHixieHixieShake() |
| { |
| byte[] result=WebSocketConnectionD00.doTheHixieHixieShake( |
| WebSocketConnectionD00.hixieCrypt(_key1), |
| WebSocketConnectionD00.hixieCrypt(_key2), |
| _hixieBytes.asArray()); |
| _hixieBytes.clear(); |
| _hixieBytes.put(result); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean isOpen() |
| { |
| return _endp!=null&&_endp.isOpen(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean isIdle() |
| { |
| return _parser.isBufferEmpty() && _generator.isBufferEmpty(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean isSuspended() |
| { |
| return false; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void onClose() |
| { |
| _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,""); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| */ |
| public void sendMessage(String content) throws IOException |
| { |
| byte[] data = content.getBytes(StringUtil.__UTF8); |
| _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length); |
| _generator.flush(); |
| checkWriteable(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void sendMessage(byte[] data, int offset, int length) throws IOException |
| { |
| _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length); |
| _generator.flush(); |
| checkWriteable(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean isMore(byte flags) |
| { |
| return (flags&0x8) != 0; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * {@inheritDoc} |
| */ |
| public void sendControl(byte code, byte[] content, int offset, int length) throws IOException |
| { |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException |
| { |
| _generator.addFrame((byte)0,opcode,content,offset,length); |
| _generator.flush(); |
| checkWriteable(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void close(int code, String message) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void disconnect() |
| { |
| close(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void close() |
| { |
| try |
| { |
| _generator.flush(); |
| _endp.close(); |
| } |
| catch(IOException e) |
| { |
| LOG.ignore(e); |
| } |
| } |
| |
| public void shutdown() |
| { |
| close(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void fillBuffersFrom(Buffer buffer) |
| { |
| _parser.fill(buffer); |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| private void checkWriteable() |
| { |
| if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint) |
| ((AsyncEndPoint)_endp).scheduleWrite(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| static long hixieCrypt(String key) |
| { |
| // Don't ask me what all this is about. |
| // I think it's pretend secret stuff, kind of |
| // like talking in pig latin! |
| long number=0; |
| int spaces=0; |
| for (char c : key.toCharArray()) |
| { |
| if (Character.isDigit(c)) |
| number=number*10+(c-'0'); |
| else if (c==' ') |
| spaces++; |
| } |
| return number/spaces; |
| } |
| |
| public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3) |
| { |
| try |
| { |
| MessageDigest md = MessageDigest.getInstance("MD5"); |
| byte [] fodder = new byte[16]; |
| |
| fodder[0]=(byte)(0xff&(key1>>24)); |
| fodder[1]=(byte)(0xff&(key1>>16)); |
| fodder[2]=(byte)(0xff&(key1>>8)); |
| fodder[3]=(byte)(0xff&key1); |
| fodder[4]=(byte)(0xff&(key2>>24)); |
| fodder[5]=(byte)(0xff&(key2>>16)); |
| fodder[6]=(byte)(0xff&(key2>>8)); |
| fodder[7]=(byte)(0xff&key2); |
| System.arraycopy(key3, 0, fodder, 8, 8); |
| md.update(fodder); |
| return md.digest(); |
| } |
| catch (NoSuchAlgorithmException e) |
| { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| public void setMaxTextMessageSize(int size) |
| { |
| } |
| |
| public void setMaxIdleTime(int ms) |
| { |
| try |
| { |
| _endp.setMaxIdleTime(ms); |
| } |
| catch(IOException e) |
| { |
| LOG.warn(e); |
| } |
| } |
| |
| public void setMaxBinaryMessageSize(int size) |
| { |
| } |
| |
| public int getMaxTextMessageSize() |
| { |
| return -1; |
| } |
| |
| public int getMaxIdleTime() |
| { |
| return _endp.getMaxIdleTime(); |
| } |
| |
| public int getMaxBinaryMessageSize() |
| { |
| return -1; |
| } |
| |
| public String getProtocol() |
| { |
| return _protocol; |
| } |
| |
| protected void onFrameHandshake() |
| { |
| if (_websocket instanceof OnFrame) |
| { |
| ((OnFrame)_websocket).onHandshake(this); |
| } |
| } |
| |
| protected void onWebsocketOpen() |
| { |
| _websocket.onOpen(this); |
| } |
| |
| static class FrameHandlerD00 implements WebSocketParser.FrameHandler |
| { |
| final WebSocket _websocket; |
| |
| FrameHandlerD00(WebSocket websocket) |
| { |
| _websocket=websocket; |
| } |
| |
| public void onFrame(byte flags, byte opcode, Buffer buffer) |
| { |
| try |
| { |
| byte[] array=buffer.array(); |
| |
| if (opcode==0) |
| { |
| if (_websocket instanceof WebSocket.OnTextMessage) |
| ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8)); |
| } |
| else |
| { |
| if (_websocket instanceof WebSocket.OnBinaryMessage) |
| ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length()); |
| } |
| } |
| catch(Throwable th) |
| { |
| LOG.warn(th); |
| } |
| } |
| |
| public void close(int code,String message) |
| { |
| } |
| } |
| |
| public boolean isMessageComplete(byte flags) |
| { |
| return true; |
| } |
| |
| public byte binaryOpcode() |
| { |
| return LENGTH_FRAME; |
| } |
| |
| public byte textOpcode() |
| { |
| return SENTINEL_FRAME; |
| } |
| |
| public boolean isControl(byte opcode) |
| { |
| return false; |
| } |
| |
| public boolean isText(byte opcode) |
| { |
| return (opcode&LENGTH_FRAME)==0; |
| } |
| |
| public boolean isBinary(byte opcode) |
| { |
| return (opcode&LENGTH_FRAME)!=0; |
| } |
| |
| public boolean isContinuation(byte opcode) |
| { |
| return false; |
| } |
| |
| public boolean isClose(byte opcode) |
| { |
| return false; |
| } |
| |
| public boolean isPing(byte opcode) |
| { |
| return false; |
| } |
| |
| public boolean isPong(byte opcode) |
| { |
| return false; |
| } |
| |
| public List<Extension> getExtensions() |
| { |
| return Collections.emptyList(); |
| } |
| |
| public byte continuationOpcode() |
| { |
| return 0; |
| } |
| |
| public byte finMask() |
| { |
| return 0; |
| } |
| |
| public void setAllowFrameFragmentation(boolean allowFragmentation) |
| { |
| } |
| |
| public boolean isAllowFrameFragmentation() |
| { |
| return false; |
| } |
| } |