Repository: tootallnate/java-websocket
Issue: Continuous binary getting swallowed?

My WS JS hosted in Chrome seems to be sending the following frames:
BINARY, isFin = false
CONTINUOUS, isFin = true

Java-WebSocket seems to drop the CONTINUOUS frame on the floor and the application never gets it:

```java
// org/java_websocket/drafts/Draft_6455.java:542
} else if( frame.isFin() ) {
	if( current_continuous_frame == null )
		throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
	//Check if the whole payload is valid utf8, when the opcode indicates a text
	if( current_continuous_frame.getOpcode() == Framedata.Opcode.TEXT ) {
		//Checking a bit more from the frame before this one just to make sure all the code points are correct
		int off = Math.max( current_continuous_frame.getPayloadData().limit() - 64, 0 );
		current_continuous_frame.append( frame );
		if( !Charsetfunctions.isValidUTF8( current_continuous_frame.getPayloadData(), off ) ) {
			throw new InvalidDataException( CloseFrame.NO_UTF8 );
		}
	}
	// **** What about if the current_continuous_frame.getOpcode() == Framedata.Opcode.BINARY ****//
	current_continuous_frame = null;
```

Is this expected? Is Chrome breaking the spec? Is this something Java-WebSocket should handle differently?

Changed files:
src/main/java/org/java_websocket/WebSocketListener.java, src/main/java/org/java_websocket/WebSocketImpl.java, src/main/java/org/java_websocket/drafts/Draft_6455.java, src/main/java/org/java_websocket/server/WebSocketServer.java, src/test/java/org/java_websocket/example/AutobahnServerTest.java

diff --git a/src/main/java/org/java_websocket/WebSocketImpl.java b/src/main/java/org/java_websocket/WebSocketImpl.java
index 74aca19..f794591 100644
--- a/src/main/java/org/java_websocket/WebSocketImpl.java
+++ b/src/main/java/org/java_websocket/WebSocketImpl.java
@@ -605,7 +605,7 @@ public class WebSocketImpl implements WebSocket {
 		if( !isOpen() ) {
 			throw new WebsocketNotConnectedException();
 		}
-		if( frames == null || frames.isEmpty() ) {
+		if( frames == null) {
 			throw new IllegalArgumentException();
 		}
 		ArrayList<ByteBuffer> outgoingFrames = new ArrayList<ByteBuffer>();
diff --git a/src/main/java/org/java_websocket/WebSocketListener.java b/src/main/java/org/java_websocket/WebSocketListener.java
index d865504..fd19883 100644
--- a/src/main/java/org/java_websocket/WebSocketListener.java
+++ b/src/main/java/org/java_websocket/WebSocketListener.java
@@ -59,7 +59,7 @@ public interface WebSocketListener {
 	 * @throws InvalidDataException
 	 *             Throwing this exception will cause this handshake to be rejected
 	 */
-	public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException;
+	ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException;
 
 	/**
 	 * Called on the client side when the socket connection is first established, and the WebSocketImpl
@@ -74,7 +74,7 @@ public interface WebSocketListener {
 	 * @throws InvalidDataException
 	 *             Allows the client to reject the connection with the server in respect of its handshake response.
 	 */
-	public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException;
+	void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException;
 
 	/**
 	 * Called on the client side when the socket connection is first established, and the WebSocketImpl
@@ -87,7 +87,7 @@ public interface WebSocketListener {
 	 * @throws InvalidDataException
 	 *             Allows the client to stop the connection from progressing
 	 */
-	public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException;
+	void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException;
 
 	/**
 	 * Called when an entire text frame has been received. Do whatever you want
@@ -98,7 +98,7 @@ public interface WebSocketListener {
 	 * @param message
 	 *            The UTF-8 decoded message that was received.
 	 */
-	public void onWebsocketMessage( WebSocket conn, String message );
+	void onWebsocketMessage( WebSocket conn, String message );
 
 	/**
 	 * Called when an entire binary frame has been received. Do whatever you want
@@ -109,16 +109,18 @@ public interface WebSocketListener {
 	 * @param blob
 	 *            The binary message that was received.
 	 */
-	public void onWebsocketMessage( WebSocket conn, ByteBuffer blob );
+	void onWebsocketMessage( WebSocket conn, ByteBuffer blob );
 
 	/**
 	 * Called when a frame fragment has been recieved
 	 *
+	 * This method will be removed in a future version since the lib will also call the respective onWebsocketMessage method
 	 * @param conn
 	 *            The <tt>WebSocket</tt> instance this event is occurring on.
 	 * @param frame The fragmented frame
 	 */
-	public void onWebsocketMessageFragment( WebSocket conn, Framedata frame );
+	@Deprecated
+	void onWebsocketMessageFragment( WebSocket conn, Framedata frame );
 
 	/**
 	 * Called after <var>onHandshakeReceived</var> returns <var>true</var>.
@@ -128,7 +130,7 @@ public interface WebSocketListener {
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 * @param d The handshake of the websocket instance
 	 */
-	public void onWebsocketOpen( WebSocket conn, Handshakedata d );
+	void onWebsocketOpen( WebSocket conn, Handshakedata d );
 
 	/**
 	 * Called after <tt>WebSocket#close</tt> is explicity called, or when the
@@ -139,7 +141,7 @@ public interface WebSocketListener {
 	 * @param reason Additional information string
 	 * @param remote Returns whether or not the closing of the connection was initiated by the remote host.
 	 */
-	public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote );
+	void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote );
 
 	/** Called as soon as no further frames are accepted
 	 *
@@ -148,7 +150,7 @@ public interface WebSocketListener {
 	 * @param reason Additional information string
 	 * @param remote Returns whether or not the closing of the connection was initiated by the remote host.
 	 */
-	public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote );
+	void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote );
 
 	/** send when this peer sends a close handshake
 	 *
@@ -156,7 +158,7 @@ public interface WebSocketListener {
 	 * @param code The codes can be looked up here: {@link CloseFrame}
 	 * @param reason Additional information string
 	 */
-	public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason );
+	void onWebsocketCloseInitiated( WebSocket ws, int code, String reason );
 
 	/**
 	 * Called if an exception worth noting occurred.
@@ -167,7 +169,7 @@ public interface WebSocketListener {
 	 *            The exception that occurred. <br>
 	 *            Might be null if the exception is not related to any specific connection. For example if the server port could not be bound.
 	 */
-	public void onWebsocketError( WebSocket conn, Exception ex );
+	void onWebsocketError( WebSocket conn, Exception ex );
 
 	/**
 	 * Called a ping frame has been received.
@@ -176,7 +178,7 @@ public interface WebSocketListener {
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 * @param f The ping frame. Control frames may contain payload.
 	 */
-	public void onWebsocketPing( WebSocket conn, Framedata f );
+	void onWebsocketPing( WebSocket conn, Framedata f );
 
 	/**
 	 * Called when a pong frame is received.
@@ -184,7 +186,7 @@ public interface WebSocketListener {
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 * @param f The pong frame. Control frames may contain payload.
 	 **/
-	public void onWebsocketPong( WebSocket conn, Framedata f );
+	void onWebsocketPong( WebSocket conn, Framedata f );
 
 	/**
 	 * @see WebSocketAdapter#getFlashPolicy(WebSocket)
@@ -192,12 +194,13 @@ public interface WebSocketListener {
 	 * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained.
 	 * @return An XML String that comforts to Flash's security policy. You MUST not include the null char at the end, it is appended automatically.
 	 */
-	public String getFlashPolicy( WebSocket conn ) throws InvalidDataException;
+	@Deprecated
+	String getFlashPolicy( WebSocket conn ) throws InvalidDataException;
 
 	/** This method is used to inform the selector thread that there is data queued to be written to the socket.
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 */
-	public void onWriteDemand( WebSocket conn );
+	void onWriteDemand( WebSocket conn );
 
 	/**
 	 * @see  WebSocket#getLocalSocketAddress()
@@ -205,7 +208,7 @@ public interface WebSocketListener {
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 * @return Returns the address of the endpoint this socket is bound to.
 	 */
-	public InetSocketAddress getLocalSocketAddress( WebSocket conn );
+	InetSocketAddress getLocalSocketAddress( WebSocket conn );
 
 	/**
 	 * @see  WebSocket#getRemoteSocketAddress()
@@ -213,5 +216,5 @@ public interface WebSocketListener {
 	 * @param conn The <tt>WebSocket</tt> instance this event is occuring on.
 	 * @return Returns the address of the endpoint this socket is connected to, or{@code null} if it is unconnected.
 	 */
-	public InetSocketAddress getRemoteSocketAddress( WebSocket conn );
+	InetSocketAddress getRemoteSocketAddress( WebSocket conn );
 }
diff --git a/src/main/java/org/java_websocket/drafts/Draft_6455.java b/src/main/java/org/java_websocket/drafts/Draft_6455.java
index e44e72f..03bb639 100644
--- a/src/main/java/org/java_websocket/drafts/Draft_6455.java
+++ b/src/main/java/org/java_websocket/drafts/Draft_6455.java
@@ -62,6 +62,11 @@ public class Draft_6455 extends Draft {
 	 */
 	private Framedata current_continuous_frame;
 
+	/**
+	 * Attribute for the payload of the current continuous frame
+	 */
+	private List<ByteBuffer> byteBufferList;
+
 	/**
 	 * Attribute for the current incomplete frame
 	 */
@@ -96,6 +101,7 @@ public class Draft_6455 extends Draft {
 	public Draft_6455( List<IExtension> inputExtensions ) {
 		knownExtensions = new ArrayList<IExtension>();
 		boolean hasDefault = false;
+		byteBufferList = new ArrayList<ByteBuffer>();
 		for( IExtension inputExtension : inputExtensions ) {
 			if( inputExtension.getClass().equals( DefaultExtension.class ) ) {
 				hasDefault = true;
@@ -548,19 +554,30 @@ public class Draft_6455 extends Draft {
 				if( current_continuous_frame != null )
 					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." );
 				current_continuous_frame = frame;
+				byteBufferList.add( frame.getPayloadData() );
 			} else if( frame.isFin() ) {
 				if( current_continuous_frame == null )
 					throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
-				//Check if the whole payload is valid utf8, when the opcode indicates a text
+				byteBufferList.add( frame.getPayloadData() );
 				if( current_continuous_frame.getOpcode() == Framedata.Opcode.TEXT ) {
-					//Checking a bit more from the frame before this one just to make sure all the code points are correct
-					int off = Math.max( current_continuous_frame.getPayloadData().limit() - 64, 0 );
-					current_continuous_frame.append( frame );
-					if( !Charsetfunctions.isValidUTF8( current_continuous_frame.getPayloadData(), off ) ) {
-						throw new InvalidDataException( CloseFrame.NO_UTF8 );
+					((FramedataImpl1) current_continuous_frame).setPayload( getPayloadFromByteBufferList() );
+					((FramedataImpl1) current_continuous_frame ).isValid();
+					try {
+						webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, Charsetfunctions.stringUtf8( current_continuous_frame.getPayloadData() ) );
+					} catch ( RuntimeException e ) {
+						webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
+					}
+				} else if( current_continuous_frame.getOpcode() == Framedata.Opcode.BINARY ) {
+					((FramedataImpl1) current_continuous_frame).setPayload( getPayloadFromByteBufferList() );
+					((FramedataImpl1) current_continuous_frame ).isValid();
+					try {
+						webSocketImpl.getWebSocketListener().onWebsocketMessage( webSocketImpl, current_continuous_frame.getPayloadData() );
+					} catch ( RuntimeException e ) {
+						webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
 					}
 				}
 				current_continuous_frame = null;
+				byteBufferList.clear();
 			} else if( current_continuous_frame == null ) {
 				throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." );
 			}
@@ -571,18 +588,8 @@ public class Draft_6455 extends Draft {
 				}
 			}
 			//Checking if the current continous frame contains a correct payload with the other frames combined
-			if( curop == Framedata.Opcode.CONTINUOUS && current_continuous_frame != null && current_continuous_frame.getOpcode() == Framedata.Opcode.TEXT ) {
-				//Checking a bit more from the frame before this one just to make sure all the code points are correct
-				int off = Math.max( current_continuous_frame.getPayloadData().limit() - 64, 0 );
-				current_continuous_frame.append( frame );
-				if( !Charsetfunctions.isValidUTF8( current_continuous_frame.getPayloadData(), off ) ) {
-					throw new InvalidDataException( CloseFrame.NO_UTF8 );
-				}
-			}
-			try {
-				webSocketImpl.getWebSocketListener().onWebsocketMessageFragment( webSocketImpl, frame );
-			} catch ( RuntimeException e ) {
-				webSocketImpl.getWebSocketListener().onWebsocketError( webSocketImpl, e );
+			if( curop == Framedata.Opcode.CONTINUOUS && current_continuous_frame != null ) {
+				byteBufferList.add( frame.getPayloadData() );
 			}
 			return;
 		} else if( current_continuous_frame != null ) {
@@ -632,4 +639,25 @@ public class Draft_6455 extends Draft {
 	public int hashCode() {
 		return extension != null ? extension.hashCode() : 0;
 	}
+
+	/**
+	 * Method to generate a full bytebuffer out of all the fragmented frame payload
+	 * @return a bytebuffer containing all the data
+	 * @throws LimitExedeedException will be thrown when the totalSize is bigger then Integer.MAX_VALUE due to not being able to allocate more
+	 */
+	private ByteBuffer getPayloadFromByteBufferList() throws LimitExedeedException {
+		long totalSize = 0;
+		for (ByteBuffer buffer : byteBufferList) {
+			totalSize +=buffer.limit();
+		}
+		if (totalSize > Integer.MAX_VALUE) {
+			throw new LimitExedeedException( "Payloadsize is to big..." );
+		}
+		ByteBuffer resultingByteBuffer = ByteBuffer.allocate( (int) totalSize );
+		for (ByteBuffer buffer : byteBufferList) {
+			resultingByteBuffer.put( buffer );
+		}
+		resultingByteBuffer.flip();
+		return resultingByteBuffer;
+	}
 }
diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java
index 872db60..38c2a6e 100644
--- a/src/main/java/org/java_websocket/server/WebSocketServer.java
+++ b/src/main/java/org/java_websocket/server/WebSocketServer.java
@@ -767,6 +767,7 @@ public abstract class WebSocketServer extends AbstractWebSocket implements Runna
 	 *            The <tt>WebSocket</tt> instance this event is occurring on.
 	 * @param fragment The fragmented frame
 	 */
+	@Deprecated
 	public void onFragment( WebSocket conn, Framedata fragment ) {
 	}
 
diff --git a/src/test/java/org/java_websocket/example/AutobahnServerTest.java b/src/test/java/org/java_websocket/example/AutobahnServerTest.java
index 1cc752d..e8749a4 100644
--- a/src/test/java/org/java_websocket/example/AutobahnServerTest.java
+++ b/src/test/java/org/java_websocket/example/AutobahnServerTest.java
@@ -48,7 +48,6 @@ import java.util.Collections;
 
 public class AutobahnServerTest extends WebSocketServer {
 	private static int counter = 0;
-
 	public AutobahnServerTest( int port, Draft d ) throws UnknownHostException {
 		super( new InetSocketAddress( port ), Collections.singletonList( d ) );
 	}
@@ -83,17 +82,14 @@ public class AutobahnServerTest extends WebSocketServer {
 	public void onMessage( WebSocket conn, String message ) {
 		conn.send( message );
 	}
-
 	@Override
-	public void onMessage( WebSocket conn, ByteBuffer blob ) {
-		conn.send( blob );
+	public void onFragment( WebSocket conn, Framedata fragment ) {
+		System.out.println( "received fragment: " + fragment );
 	}
 
 	@Override
-	public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {
-		FramedataImpl1 builder = ( FramedataImpl1 ) frame;
-		builder.setTransferemasked( false );
-		conn.sendFrame( frame );
+	public void onMessage( WebSocket conn, ByteBuffer blob ) {
+		conn.send( blob );
 	}
 
 	public static void main( String[] args ) throws UnknownHostException {