Skip to content

Commit

Permalink
Infrastructure for websocket extension
Browse files Browse the repository at this point in the history
  • Loading branch information
marci4 committed Jul 28, 2017
1 parent 657262f commit 283c014
Show file tree
Hide file tree
Showing 9 changed files with 568 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/main/java/org/java_websocket/AbstractWebSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void setConnectionLostTimeout( int connectionLostTimeout ) {
protected void stopConnectionLostTimer() {
if (connectionLostTimer != null ||connectionLostTimerTask != null) {
if( WebSocketImpl.DEBUG )
System.out.println( "Connection lost timer stoped" );
System.out.println( "Connection lost timer stopped" );
cancelConnectionLostTimer();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/java_websocket/WebSocketImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ private void write( List<ByteBuffer> bufs ) {

private void open( Handshakedata d ) {
if( DEBUG )
System.out.println( "open using draft: " + draft.getClass().getSimpleName() );
System.out.println( "open using draft: " + draft );
readystate = READYSTATE.OPEN;
try {
wsl.onWebsocketOpen( this, d );
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/java_websocket/drafts/Draft.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,8 @@ public Role getRole() {
return role;
}

public String toString() {
return getClass().getSimpleName();
}

}
6 changes: 3 additions & 3 deletions src/main/java/org/java_websocket/drafts/Draft_10.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
@Deprecated
public class Draft_10 extends Draft {

private class IncompleteException extends Throwable {
class IncompleteException extends Throwable {

/**
* It's Serializable.
Expand Down Expand Up @@ -77,7 +77,7 @@ public static int readVersion( Handshakedata handshakedata ) {
return -1;
}

private ByteBuffer incompleteframe;
ByteBuffer incompleteframe;

private final Random reuseableRandom = new Random();

Expand Down Expand Up @@ -232,7 +232,7 @@ private byte[] toByteArray( long val, int bytecount ) {
return buffer;
}

private Opcode toOpcode( byte opcode ) throws InvalidFrameException {
Opcode toOpcode( byte opcode ) throws InvalidFrameException {
switch(opcode) {
case 0:
return Opcode.CONTINUOUS;
Expand Down
329 changes: 302 additions & 27 deletions src/main/java/org/java_websocket/drafts/Draft_6455.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,22 @@

package org.java_websocket.drafts;

import org.java_websocket.WebSocketImpl;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidFrameException;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.HandshakeBuilder;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.exceptions.LimitExedeedException;
import org.java_websocket.extensions.DefaultExtension;
import org.java_websocket.extensions.IExtension;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.FramedataImpl1;
import org.java_websocket.handshake.*;
import org.java_websocket.util.Charsetfunctions;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.*;

/**
* Implementation for the RFC 6455 websocket protocol
Expand All @@ -42,25 +49,293 @@
@SuppressWarnings("deprecation")
public class Draft_6455 extends Draft_17 {

@Override
public HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request, ServerHandshakeBuilder response) throws InvalidHandshakeException {
super.postProcessHandshakeResponseAsServer(request, response);
response.setHttpStatusMessage("Web Socket Protocol Handshake");
response.put("Server", "TooTallNate Java-WebSocket");
response.put("Date", getServerTime());
return response;
}

@Override
public Draft copyInstance() {
return new Draft_6455();
}

private String getServerTime() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat.format(calendar.getTime());
}
/**
* Attribute for the used extension in this draft
*/
private IExtension extension;

/**
* Attribute for all available extension in this draft
*/
private List<IExtension> knownExtensions;

/**
* Constructor for the websocket protocol specified by RFC 6455 with default extensions
*/
public Draft_6455() {
this( Collections.<IExtension>emptyList() );
}

/**
* Constructor for the websocket protocol specified by RFC 6455 with custom extensions
*
* @param inputExtension the extension which should be used for this draft
*/
public Draft_6455( IExtension inputExtension ) {
this( Collections.singletonList( inputExtension ) );
}

/**
* Constructor for the websocket protocol specified by RFC 6455 with custom extensions
*
* @param inputExtensions the extensions which should be used for this draft
*/
public Draft_6455( List<IExtension> inputExtensions ) {
knownExtensions = new ArrayList<IExtension>();
boolean hasDefault = false;
for( IExtension inputExtension : inputExtensions ) {
if( inputExtension.getClass().equals( DefaultExtension.class ) ) {
hasDefault = true;
}
}
knownExtensions.addAll( inputExtensions );
//We always add the DefaultExtension to implement the normal RFC 6455 specification
if( !hasDefault ) {
DefaultExtension defaultExtension = new DefaultExtension();
knownExtensions.add( this.knownExtensions.size(), defaultExtension );
}
}

@Override
public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException {
if( super.acceptHandshakeAsServer( handshakedata ) == HandshakeState.NOT_MATCHED ) {
return HandshakeState.NOT_MATCHED;
}
String requestedExtension = handshakedata.getFieldValue( "Sec-WebSocket-Extensions" );
for( IExtension knownExtension : knownExtensions ) {
if( knownExtension.acceptProvidedExtensionAsServer( requestedExtension ) ) {
extension = knownExtension;
return HandshakeState.MATCHED;
}
}
return HandshakeState.NOT_MATCHED;
}


@Override
public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException {
if( super.acceptHandshakeAsClient( request, response ) == HandshakeState.NOT_MATCHED ) {
return HandshakeState.NOT_MATCHED;
}
String requestedExtension = response.getFieldValue( "Sec-WebSocket-Extensions" );
for( IExtension knownExtension : knownExtensions ) {
if( knownExtension.acceptProvidedExtensionAsClient( requestedExtension ) ) {
extension = knownExtension;
return HandshakeState.MATCHED;
}
}
return HandshakeState.NOT_MATCHED;
}

/**
* Getter for the extension which is used by this draft
*
* @return the extension which is used or null, if handshake is not yet done
*/
public IExtension getExtension() {
return extension;
}

@Override
public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
super.postProcessHandshakeRequestAsClient( request );
StringBuilder requestedExtensions = new StringBuilder();
for( IExtension knownExtension : knownExtensions ) {
if( knownExtension.getProvidedExtensionAsClient() != null && !knownExtension.getProvidedExtensionAsClient().equals( "" ) ) {
requestedExtensions.append( knownExtension.getProvidedExtensionAsClient() ).append( "; " );
}
}
if( requestedExtensions.length() != 0 ) {
request.put( "Sec-WebSocket-Extensions", requestedExtensions.toString() );
}
return request;
}

@Override
public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder
response ) throws InvalidHandshakeException {
super.postProcessHandshakeResponseAsServer( request, response );
if( getExtension().getProvidedExtensionAsServer().length() != 0 ) {
response.put( "Sec-WebSocket-Extensions", getExtension().getProvidedExtensionAsServer() );
}
response.setHttpStatusMessage( "Web Socket Protocol Handshake" );
response.put( "Server", "TooTallNate Java-WebSocket" );
response.put( "Date", getServerTime() );
return response;
}


@Override
public Draft copyInstance() {
ArrayList<IExtension> newExtensions = new ArrayList<IExtension>();
for( IExtension extension : knownExtensions ) {
newExtensions.add( extension.copyInstance() );
}
return new Draft_6455( newExtensions );
}

@Override
public ByteBuffer createBinaryFrame( Framedata framedata ) {
getExtension().encodeFrame( framedata );
return super.createBinaryFrame( framedata );
}

@Override
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;
boolean rsv1 = false, rsv2 = false, rsv3 = false;
if( ( b1 & 0x40 ) != 0 ) {
rsv1 = true;
}
if( ( b1 & 0x20 ) != 0 ) {
rsv2 = true;
}
if( ( b1 & 0x10 ) != 0 ) {
rsv3 = true;
}
byte b2 = buffer.get( /*1*/ );
boolean MASK = ( b2 & -128 ) != 0;
int payloadlength = ( byte ) ( b2 & ~( byte ) 128 );
Framedata.Opcode optcode = toOpcode( ( byte ) ( b1 & 15 ) );

if( !( payloadlength >= 0 && payloadlength <= 125 ) ) {
if( optcode == Framedata.Opcode.PING || optcode == Framedata.Opcode.PONG || optcode == Framedata.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 ) ( buffer.get( /*payloadstart + i*/ ) ^ maskskey[i % 4] ) );
}
} else {
payload.put( buffer.array(), buffer.position(), payload.limit() );
buffer.position( buffer.position() + payload.limit() );
}

FramedataImpl1 frame = FramedataImpl1.get( optcode );
frame.setFin( FIN );
frame.setRSV1( rsv1 );
frame.setRSV2( rsv2 );
frame.setRSV3( rsv3 );
payload.flip();
frame.setPayload( payload );
getExtension().isFrameValid( frame );
getExtension().decodeFrame( frame );
if( WebSocketImpl.DEBUG )
System.out.println( "Decode Payload after: " + Arrays.toString( Charsetfunctions.utf8Bytes( new String( frame.getPayloadData().array() ) ) ) );
frame.isValid();
return frame;
}


@Override
public List<Framedata> translateFrame( ByteBuffer buffer ) throws InvalidDataException {
while( true ) {
List<Framedata> frames = new LinkedList<Framedata>();
Framedata cur;
if( incompleteframe != null ) {
// complete an incomplete frame
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;
} 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;
continue;
}
}

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;
}
}

/**
* Generate a date for for the date-header
*
* @return the server time
*/
private String getServerTime() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US );
dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
return dateFormat.format( calendar.getTime() );
}

@Override
public String toString() {
return super.toString() + " extension: " + getExtension().toString();
}
}
Loading

0 comments on commit 283c014

Please sign in to comment.