package org.java_websocket.drafts; import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidFrameException; import org.java_websocket.exceptions.InvalidHandshakeException; import org.java_websocket.exceptions.LimitExedeedException; import org.java_websocket.exceptions.NotSendableException; import org.java_websocket.framing.CloseFrame; import org.java_websocket.framing.FrameBuilder; import org.java_websocket.framing.Framedata; import org.java_websocket.framing.Framedata.Opcode; import org.java_websocket.framing.FramedataImpl1; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.handshake.ClientHandshakeBuilder; import org.java_websocket.handshake.HandshakeBuilder; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; import org.java_websocket.util.Charsetfunctions; public class Draft_75 extends Draft { /** * The byte representing CR, or Carriage Return, or \r */ public static final byte CR = (byte) 0x0D; /** * The byte representing LF, or Line Feed, or \n */ public static final byte LF = (byte) 0x0A; /** * The byte representing the beginning of a WebSocket text frame. */ public static final byte START_OF_FRAME = (byte) 0x00; /** * The byte representing the end of a WebSocket text frame. */ public static final byte END_OF_FRAME = (byte) 0xFF; /** Is only used to detect protocol violations */ protected boolean readingState = false; protected List<Framedata> readyframes = new LinkedList<Framedata>(); protected ByteBuffer currentFrame; private final Random reuseableRandom = new Random(); @Override public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; } @Override public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) { return HandshakeState.MATCHED; } return HandshakeState.NOT_MATCHED; } @Override public ByteBuffer createBinaryFrame( Framedata framedata ) { if( framedata.getOpcode() != Opcode.TEXT ) { throw new RuntimeException( "only text frames supported" ); } ByteBuffer pay = framedata.getPayloadData(); ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 ); b.put( START_OF_FRAME ); pay.mark(); b.put( pay ); pay.reset(); b.put( END_OF_FRAME ); b.flip(); return b; } @Override public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { throw new RuntimeException( "not yet implemented" ); } @Override public List<Framedata> createFrames( String text, boolean mask ) { FrameBuilder frame = new FramedataImpl1(); try { frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); } catch ( InvalidDataException e ) { throw new NotSendableException( e ); } frame.setFin( true ); frame.setOptcode( Opcode.TEXT ); frame.setTransferemasked( mask ); return Collections.singletonList( (Framedata) frame ); } @Override public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException { request.put( "Upgrade", "WebSocket" ); request.put( "Connection", "Upgrade" ); if( !request.hasFieldValue( "Origin" ) ) { request.put( "Origin", "random" + reuseableRandom.nextInt() ); } return request; } @Override public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); response.put( "Upgrade", "WebSocket" ); response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) ); String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); response.put( "WebSocket-Location", location ); // TODO handle Sec-WebSocket-Protocol and Set-Cookie return response; } protected List<Framedata> translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { while ( buffer.hasRemaining() ) { byte newestByte = buffer.get(); if( newestByte == START_OF_FRAME ) { // Beginning of Frame if( readingState ) throw new InvalidFrameException( "unexpected START_OF_FRAME" ); readingState = true; } else if( newestByte == END_OF_FRAME ) { // End of Frame if( !readingState ) throw new InvalidFrameException( "unexpected END_OF_FRAME" ); // currentFrame will be null if END_OF_FRAME was send directly after // START_OF_FRAME, thus we will send 'null' as the sent message. if( this.currentFrame != null ) { currentFrame.flip(); FramedataImpl1 curframe = new FramedataImpl1(); curframe.setPayload( currentFrame ); curframe.setFin( true ); curframe.setOptcode( Opcode.TEXT ); readyframes.add( curframe ); this.currentFrame = null; buffer.mark(); } readingState = false; } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow if( currentFrame == null ) { currentFrame = createBuffer(); } else if( !currentFrame.hasRemaining() ) { currentFrame = increaseBuffer( currentFrame ); } currentFrame.put( newestByte ); } else { return null; } } // if no error occurred this block will be reached /*if( readingState ) { checkAlloc(currentFrame.position()+1); }*/ List<Framedata> frames = readyframes; readyframes = new LinkedList<Framedata>(); return frames; } @Override public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException { List<Framedata> frames = translateRegularFrame( buffer ); if( frames == null ) { throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR ); } return frames; } @Override public void reset() { readingState = false; this.currentFrame = null; } @Override public CloseHandshakeType getCloseHandshakeType() { return CloseHandshakeType.NONE; } public ByteBuffer createBuffer() { return ByteBuffer.allocate( INITIAL_FAMESIZE ); } public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { full.flip(); ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); newbuffer.put( full ); return newbuffer; } @Override public Draft copyInstance() { return new Draft_75(); } }