Skip to content

Infrastructure for websocket extension #527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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