// ---------------------------------------------------------------------------
// jWebSocket - WebSocket Protocol Handler
// Copyright (c) 2010 Innotrade GmbH, jWebSocket.org
// ---------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
// more details.
// You should have received a copy of the GNU Lesser General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
// ---------------------------------------------------------------------------
package org.jwebsocket.kit;
import org.jwebsocket.api.WebSocketPacket;
import org.jwebsocket.config.JWebSocketCommonConstants;
import java.util.List;
/**
* Utility class for packetizing WebSocketPacket into web socket protocol packet or packets (with fragmentation) and
* vice versa.
* <p/>
* <p>
* Web socket protocol packet specification
* (see: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03):
* </p>
* <pre>
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-------+-+-------------+-------------------------------+
* |M|R|R|R| opcode|R| Payload len | Extended payload length |
* |O|S|S|S| (4) |S| (7) | (16/63) |
* |R|V|V|V| |V| | (if payload len==126/127) |
* |E|1|2|3| |4| | |
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
* | Extended payload length continued, if payload len == 127 |
* + - - - - - - - - - - - - - - - +-------------------------------+
* | | Extension data |
* +-------------------------------+ - - - - - - - - - - - - - - - +
* : :
* +---------------------------------------------------------------+
* : Application data :
* +---------------------------------------------------------------+
* </pre>
*
* @author jang
*/
public class WebSocketProtocolHandler {
// web socket protocol packet types
/**
*
*/
public static final int FRAGMENT_PT = 0x00;
/**
*
*/
public static final int CLOSE_PT = 0x01;
/**
*
*/
public static final int PING_PT = 0x02;
/**
*
*/
public static final int PONG_PT = 0x03;
/**
*
*/
public static final int UTF8_PT = 0x04;
/**
*
*/
public static final int BINARY_PT = 0x05;
/**
*
* @param aDataPacket
* @return
*/
public static byte[] toProtocolPacket(WebSocketPacket aDataPacket) {
byte[] lBuff = new byte[2]; // resulting packet will have at least 2 bytes
int lType = aDataPacket.getFrameType();
int lTargetType = toWebSocketFrameType(lType);
if (lTargetType == -1) {
throw new WebSocketRuntimeException("Cannot construct a packet with unknown packet type: " + lType);
}
// just shift four bits to the left (MORE and RSVx bits are not set)
lTargetType = lTargetType << 4;
lBuff[0] = (byte) lTargetType;
int lPayloadLen = aDataPacket.getByteArray().length;
// Here, the spec allows payload length with up to 64-bit integer
// in size (that is long data type in java):
// ----
// The length of the payload: if 0-125, that is the payload length.
// If 126, the following 2 bytes interpreted as a 16 bit unsigned
// integer are the payload length. If 127, the following 8 bytes
// interpreted as a 64-bit unsigned integer (the high bit must be 0)
// are the payload length.
// ----
// However, arrays in java may only have Integer.MAX_VALUE(32-bit) elements.
// Therefore, we never set target payload length greater than signed 32-bit number
// (Integer.MAX_VALUE).
if (lPayloadLen < 126) {
lBuff[1] = (byte) (lPayloadLen << 1); // just write the payload length
} else if (lPayloadLen > 126 && lPayloadLen < 0xFFFF) {
// first write 126 (meaning, there will follow two bytes for actual length)
lBuff[1] = (byte) (126 << 1);
int lSize = lBuff.length;
lBuff = copyOf(lBuff, lSize + 2);
lBuff[lSize] = (byte) ((lPayloadLen >>> 8) & 0xFF);
lBuff[lSize + 1] = (byte) (lPayloadLen & 0xFF);
} else if (lPayloadLen > 0xFFFF) {
// first write 127 (meaning, there will follow eight bytes for actual length)
lBuff[1] = (byte) (127 << 1);
long len = (long) lPayloadLen;
int lSize = lBuff.length;
lBuff = copyOf(lBuff, lSize + 8);
lBuff[lSize] = (byte) (len >>> 56);
lBuff[lSize + 1] = (byte) (len >>> 48);
lBuff[lSize + 2] = (byte) (len >>> 40);
lBuff[lSize + 3] = (byte) (len >>> 32);
lBuff[lSize + 4] = (byte) (len >>> 24);
lBuff[lSize + 5] = (byte) (len >>> 16);
lBuff[lSize + 6] = (byte) (len >>> 8);
lBuff[lSize + 7] = (byte) len;
}
int lSize = lBuff.length;
lBuff = copyOf(lBuff, lSize + aDataPacket.getByteArray().length);
System.arraycopy(aDataPacket.getByteArray(), 0, lBuff, lSize, aDataPacket.getByteArray().length);
return lBuff;
}
/* TODO: implement fragmentation */
/**
*
* @param aSrc
* @param aFragmentSize
* @return
*/
public static List<byte[]> toProtocolPacketFragmented(WebSocketPacket aSrc, int aFragmentSize) {
throw new UnsupportedOperationException("Fragmentation is currently not supported");
}
/**
*
* @param aWebSocketFrameType
* @return
*/
public static int toRawPacketType(int aWebSocketFrameType) {
switch (aWebSocketFrameType) {
case FRAGMENT_PT:
return RawPacket.FRAMETYPE_FRAGMENT;
case CLOSE_PT:
return RawPacket.FRAMETYPE_CLOSE;
case PING_PT:
return RawPacket.FRAMETYPE_PING;
case PONG_PT:
return RawPacket.FRAMETYPE_PONG;
case UTF8_PT:
return RawPacket.FRAMETYPE_UTF8;
case BINARY_PT:
return RawPacket.FRAMETYPE_BINARY;
// other types are reserved for future use
default:
return -1;
}
}
/**
*
* @param aJWebSocketFormatConstant
* @return
*/
public static int toRawPacketType(String aJWebSocketFormatConstant) {
return JWebSocketCommonConstants.WS_FORMAT_BINARY.equals(aJWebSocketFormatConstant)
? RawPacket.FRAMETYPE_BINARY
// treat everything else as utf8 packet type
: RawPacket.FRAMETYPE_UTF8;
}
/**
*
* @param aRawPacketType
* @return
*/
public static int toWebSocketFrameType(int aRawPacketType) {
switch (aRawPacketType) {
case RawPacket.FRAMETYPE_CLOSE:
return CLOSE_PT;
case RawPacket.FRAMETYPE_PING:
return PING_PT;
case RawPacket.FRAMETYPE_PONG:
return PONG_PT;
case RawPacket.FRAMETYPE_UTF8:
return UTF8_PT;
case RawPacket.FRAMETYPE_BINARY:
return BINARY_PT;
default:
return -1;
}
}
private static byte[] copyOf(byte[] aOriginal, int aNewLength) {
byte[] lCopy = new byte[aNewLength];
System.arraycopy(aOriginal, 0, lCopy, 0, Math.min(aOriginal.length, aNewLength));
return lCopy;
}
}