package org.java_websocket.drafts; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.java_websocket.WebSocket.Role; 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.CloseFrameBuilder; 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.Handshakedata; import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshakeBuilder; import org.java_websocket.util.Base64; import org.java_websocket.util.Charsetfunctions; public class Draft_10 extends Draft { private class IncompleteException extends Throwable { /** * It's Serializable. */ private static final long serialVersionUID = 7330519489840500997L; private int preferedsize; public IncompleteException( int preferedsize ) { this.preferedsize = preferedsize; } public int getPreferedSize() { return preferedsize; } } public static int readVersion( Handshakedata handshakedata ) { String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" ); if( vers.length() > 0 ) { int v; try { v = new Integer( vers.trim() ); return v; } catch ( NumberFormatException e ) { return -1; } } return -1; } private ByteBuffer incompleteframe; private Framedata fragmentedframe = null; private final Random reuseableRandom = new Random(); @Override public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) ) return HandshakeState.NOT_MATCHED; String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" ); String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" ); seckey_challenge = generateFinalKey( seckey_challenge ); if( seckey_challenge.equals( seckey_answere ) ) return HandshakeState.MATCHED; return HandshakeState.NOT_MATCHED; } @Override public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { // Sec-WebSocket-Origin is only required for browser clients int v = readVersion( handshakedata ); if( v == 7 || v == 8 )// g return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; return HandshakeState.NOT_MATCHED; } @Override public ByteBuffer createBinaryFrame( Framedata framedata ) { ByteBuffer mes = framedata.getPayloadData(); boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked(); int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() ); byte optcode = fromOpcode( framedata.getOpcode() ); byte one = (byte) ( framedata.isFin() ? -128 : 0 ); one |= optcode; buf.put( one ); byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes ); assert ( payloadlengthbytes.length == sizebytes ); if( sizebytes == 1 ) { buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) ); } else if( sizebytes == 2 ) { buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) ); buf.put( payloadlengthbytes ); } else if( sizebytes == 8 ) { buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) ); buf.put( payloadlengthbytes ); } else throw new RuntimeException( "Size representation not supported/specified" ); if( mask ) { ByteBuffer maskkey = ByteBuffer.allocate( 4 ); maskkey.putInt( reuseableRandom.nextInt() ); buf.put( maskkey.array() ); for( int i = 0 ; mes.hasRemaining() ; i++ ) { buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); } } else buf.put( mes ); // translateFrame ( buf.array () , buf.array ().length ); assert ( buf.remaining() == 0 ) : buf.remaining(); buf.flip(); return buf; } @Override public List<Framedata> createFrames( ByteBuffer binary, boolean mask ) { FrameBuilder curframe = new FramedataImpl1(); try { curframe.setPayload( binary ); } catch ( InvalidDataException e ) { throw new NotSendableException( e ); } curframe.setFin( true ); curframe.setOptcode( Opcode.BINARY ); curframe.setTransferemasked( mask ); return Collections.singletonList( (Framedata) curframe ); } @Override public List<Framedata> createFrames( String text, boolean mask ) { FrameBuilder curframe = new FramedataImpl1(); try { curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); } catch ( InvalidDataException e ) { throw new NotSendableException( e ); } curframe.setFin( true ); curframe.setOptcode( Opcode.TEXT ); curframe.setTransferemasked( mask ); return Collections.singletonList( (Framedata) curframe ); } private byte fromOpcode( Opcode opcode ) { if( opcode == Opcode.CONTINUOUS ) return 0; else if( opcode == Opcode.TEXT ) return 1; else if( opcode == Opcode.BINARY ) return 2; else if( opcode == Opcode.CLOSING ) return 8; else if( opcode == Opcode.PING ) return 9; else if( opcode == Opcode.PONG ) return 10; throw new RuntimeException( "Don't know how to handle " + opcode.toString() ); } private String generateFinalKey( String in ) { String seckey = in.trim(); String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; MessageDigest sh1; try { sh1 = MessageDigest.getInstance( "SHA1" ); } catch ( NoSuchAlgorithmException e ) { throw new RuntimeException( e ); } return Base64.encodeBytes( sh1.digest( acc.getBytes() ) ); } @Override public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { request.put( "Upgrade", "websocket" ); request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives request.put( "Sec-WebSocket-Version", "8" ); byte[] random = new byte[ 16 ]; reuseableRandom.nextBytes( random ); request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) ); return request; } @Override public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { response.put( "Upgrade", "websocket" ); response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives response.setHttpStatusMessage( "Switching Protocols" ); String seckey = request.getFieldValue( "Sec-WebSocket-Key" ); if( seckey == null ) throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" ); response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) ); return response; } private byte[] toByteArray( long val, int bytecount ) { byte[] buffer = new byte[ bytecount ]; int highest = 8 * bytecount - 8; for( int i = 0 ; i < bytecount ; i++ ) { buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) ); } return buffer; } private Opcode toOpcode( byte opcode ) throws InvalidFrameException { switch ( opcode ) { case 0: return Opcode.CONTINUOUS; case 1: return Opcode.TEXT; case 2: return Opcode.BINARY; // 3-7 are not yet defined case 8: return Opcode.CLOSING; case 9: return Opcode.PING; case 10: return Opcode.PONG; // 11-15 are not yet defined default : throw new InvalidFrameException( "unknow optcode " + (short) opcode ); } } @Override public List<Framedata> translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException { List<Framedata> frames = new LinkedList<Framedata>(); Framedata cur; if( incompleteframe != null ) { // complete an incomplete frame while ( true ) { try { buffer.mark(); int available_next_byte_count = buffer.remaining();// The number of bytes received int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame if( expected_next_byte_count > available_next_byte_count ) { // did not receive enough bytes to complete the frame incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); buffer.position( buffer.position() + available_next_byte_count ); return Collections.emptyList(); } incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); buffer.position( buffer.position() + expected_next_byte_count ); cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) ); frames.add( cur ); incompleteframe = null; break; // go on with the normal frame receival } catch ( IncompleteException e ) { // extending as much as suggested int oldsize = incompleteframe.limit(); ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); assert ( extendedframe.limit() > incompleteframe.limit() ); incompleteframe.rewind(); extendedframe.put( incompleteframe ); incompleteframe = extendedframe; return translateFrame( buffer ); } } } while ( buffer.hasRemaining() ) {// Read as much as possible full frames buffer.mark(); try { cur = translateSingleFrame( buffer ); frames.add( cur ); } catch ( IncompleteException e ) { // remember the incomplete data buffer.reset(); int pref = e.getPreferedSize(); incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); incompleteframe.put( buffer ); break; } } return frames; } public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException { int maxpacketsize = buffer.remaining(); int realpacketsize = 2; if( maxpacketsize < realpacketsize ) throw new IncompleteException( realpacketsize ); byte b1 = buffer.get( /*0*/); boolean FIN = b1 >> 8 != 0; byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 ); if( rsv != 0 ) throw new InvalidFrameException( "bad rsv " + rsv ); byte b2 = buffer.get( /*1*/); boolean MASK = ( b2 & -128 ) != 0; int payloadlength = (byte) ( b2 & ~(byte) 128 ); Opcode optcode = toOpcode( (byte) ( b1 & 15 ) ); if( !FIN ) { if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { throw new InvalidFrameException( "control frames may no be fragmented" ); } } if( payloadlength >= 0 && payloadlength <= 125 ) { } else { if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { throw new InvalidFrameException( "more than 125 octets" ); } if( payloadlength == 126 ) { realpacketsize += 2; // additional length bytes if( maxpacketsize < realpacketsize ) throw new IncompleteException( realpacketsize ); byte[] sizebytes = new byte[ 3 ]; sizebytes[ 1 ] = buffer.get( /*1 + 1*/); sizebytes[ 2 ] = buffer.get( /*1 + 2*/); payloadlength = new BigInteger( sizebytes ).intValue(); } else { realpacketsize += 8; // additional length bytes if( maxpacketsize < realpacketsize ) throw new IncompleteException( realpacketsize ); byte[] bytes = new byte[ 8 ]; for( int i = 0 ; i < 8 ; i++ ) { bytes[ i ] = buffer.get( /*1 + i*/); } long length = new BigInteger( bytes ).longValue(); if( length > Integer.MAX_VALUE ) { throw new LimitExedeedException( "Payloadsize is to big..." ); } else { payloadlength = (int) length; } } } // int maskskeystart = foff + realpacketsize; realpacketsize += ( MASK ? 4 : 0 ); // int payloadstart = foff + realpacketsize; realpacketsize += payloadlength; if( maxpacketsize < realpacketsize ) throw new IncompleteException( realpacketsize ); ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); if( MASK ) { byte[] maskskey = new byte[ 4 ]; buffer.get( maskskey ); for( int i = 0 ; i < payloadlength ; i++ ) { payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) ); } } else { payload.put( buffer.array(), buffer.position(), payload.limit() ); buffer.position( buffer.position() + payload.limit() ); } FrameBuilder frame; if( optcode == Opcode.CLOSING ) { frame = new CloseFrameBuilder(); } else { frame = new FramedataImpl1(); frame.setFin( FIN ); frame.setOptcode( optcode ); } payload.flip(); frame.setPayload( payload ); return frame; } @Override public void reset() { incompleteframe = null; } @Override public Draft copyInstance() { return new Draft_10(); } @Override public CloseHandshakeType getCloseHandshakeType() { return CloseHandshakeType.TWOWAY; } }