diff options
Diffstat (limited to 'websocket/src/main/java/fi/iki/elonen/WebSocketFrame.java')
-rw-r--r-- | websocket/src/main/java/fi/iki/elonen/WebSocketFrame.java | 430 |
1 files changed, 0 insertions, 430 deletions
diff --git a/websocket/src/main/java/fi/iki/elonen/WebSocketFrame.java b/websocket/src/main/java/fi/iki/elonen/WebSocketFrame.java deleted file mode 100644 index 0e209df..0000000 --- a/websocket/src/main/java/fi/iki/elonen/WebSocketFrame.java +++ /dev/null @@ -1,430 +0,0 @@ -package fi.iki.elonen; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CharsetEncoder; -import java.util.Arrays; -import java.util.List; - -public class WebSocketFrame { - private OpCode opCode; - private boolean fin; - private byte[] maskingKey; - - private byte[] payload; - - private transient int _payloadLength; - private transient String _payloadString; - - private WebSocketFrame(OpCode opCode, boolean fin) { - setOpCode(opCode); - setFin(fin); - } - - public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey) { - this(opCode, fin); - setMaskingKey(maskingKey); - setBinaryPayload(payload); - } - - public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload) { - this(opCode, fin, payload, null); - } - - public WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey) throws CharacterCodingException { - this(opCode, fin); - setMaskingKey(maskingKey); - setTextPayload(payload); - } - - public WebSocketFrame(OpCode opCode, boolean fin, String payload) throws CharacterCodingException { - this(opCode, fin, payload, null); - } - - public WebSocketFrame(WebSocketFrame clone) { - setOpCode(clone.getOpCode()); - setFin(clone.isFin()); - setBinaryPayload(clone.getBinaryPayload()); - setMaskingKey(clone.getMaskingKey()); - } - - public WebSocketFrame(OpCode opCode, List<WebSocketFrame> fragments) throws WebSocketException { - setOpCode(opCode); - setFin(true); - - long _payloadLength = 0; - for (WebSocketFrame inter : fragments) { - _payloadLength += inter.getBinaryPayload().length; - } - if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { - throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); - } - this._payloadLength = (int) _payloadLength; - byte[] payload = new byte[this._payloadLength]; - int offset = 0; - for (WebSocketFrame inter : fragments) { - System.arraycopy(inter.getBinaryPayload(), 0, payload, offset, inter.getBinaryPayload().length); - offset += inter.getBinaryPayload().length; - } - setBinaryPayload(payload); - } - - // --------------------------------GETTERS--------------------------------- - - public OpCode getOpCode() { - return opCode; - } - - public void setOpCode(OpCode opcode) { - this.opCode = opcode; - } - - public boolean isFin() { - return fin; - } - - public void setFin(boolean fin) { - this.fin = fin; - } - - public boolean isMasked() { - return maskingKey != null && maskingKey.length == 4; - } - - public byte[] getMaskingKey() { - return maskingKey; - } - - public void setMaskingKey(byte[] maskingKey) { - if (maskingKey != null && maskingKey.length != 4) { - throw new IllegalArgumentException("MaskingKey " + Arrays.toString(maskingKey) + " hasn't length 4"); - } - this.maskingKey = maskingKey; - } - - public void setUnmasked() { - setMaskingKey(null); - } - - public byte[] getBinaryPayload() { - return payload; - } - - public void setBinaryPayload(byte[] payload) { - this.payload = payload; - this._payloadLength = payload.length; - this._payloadString = null; - } - - public String getTextPayload() { - if (_payloadString == null) { - try { - _payloadString = binary2Text(getBinaryPayload()); - } catch (CharacterCodingException e) { - throw new RuntimeException("Undetected CharacterCodingException", e); - } - } - return _payloadString; - } - - public void setTextPayload(String payload) throws CharacterCodingException { - this.payload = text2Binary(payload); - this._payloadLength = payload.length(); - this._payloadString = payload; - } - - // --------------------------------SERIALIZATION--------------------------- - - public static WebSocketFrame read(InputStream in) throws IOException { - byte head = (byte) checkedRead(in.read()); - boolean fin = ((head & 0x80) != 0); - OpCode opCode = OpCode.find((byte) (head & 0x0F)); - if ((head & 0x70) != 0) { - throw new WebSocketException(CloseCode.ProtocolError, "The reserved bits (" + Integer.toBinaryString(head & 0x70) + ") must be 0."); - } - if (opCode == null) { - throw new WebSocketException(CloseCode.ProtocolError, "Received frame with reserved/unknown opcode " + (head & 0x0F) + "."); - } else if (opCode.isControlFrame() && !fin) { - throw new WebSocketException(CloseCode.ProtocolError, "Fragmented control frame."); - } - - WebSocketFrame frame = new WebSocketFrame(opCode, fin); - frame.readPayloadInfo(in); - frame.readPayload(in); - if (frame.getOpCode() == OpCode.Close) { - return new CloseFrame(frame); - } else { - return frame; - } - } - - private static int checkedRead(int read) throws IOException { - if (read < 0) { - throw new EOFException(); - } - //System.out.println(Integer.toBinaryString(read) + "/" + read + "/" + Integer.toHexString(read)); - return read; - } - - - private void readPayloadInfo(InputStream in) throws IOException { - byte b = (byte) checkedRead(in.read()); - boolean masked = ((b & 0x80) != 0); - - _payloadLength = (byte) (0x7F & b); - if (_payloadLength == 126) { - // checkedRead must return int for this to work - _payloadLength = (checkedRead(in.read()) << 8 | checkedRead(in.read())) & 0xFFFF; - if (_payloadLength < 126) { - throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)"); - } - } else if (_payloadLength == 127) { - long _payloadLength = ((long) checkedRead(in.read())) << 56 | - ((long) checkedRead(in.read())) << 48 | - ((long) checkedRead(in.read())) << 40 | - ((long) checkedRead(in.read())) << 32 | - checkedRead(in.read()) << 24 | checkedRead(in.read()) << 16 | checkedRead(in.read()) << 8 | checkedRead(in.read()); - if (_payloadLength < 65536) { - throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 4byte length. (not using minimal length encoding)"); - } - if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { - throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); - } - this._payloadLength = (int) _payloadLength; - } - - if (opCode.isControlFrame()) { - if (_payloadLength > 125) { - throw new WebSocketException(CloseCode.ProtocolError, "Control frame with payload length > 125 bytes."); - } - if (opCode == OpCode.Close && _payloadLength == 1) { - throw new WebSocketException(CloseCode.ProtocolError, "Received close frame with payload len 1."); - } - } - - if (masked) { - maskingKey = new byte[4]; - int read = 0; - while (read < maskingKey.length) { - read += checkedRead(in.read(maskingKey, read, maskingKey.length - read)); - } - } - } - - private void readPayload(InputStream in) throws IOException { - payload = new byte[_payloadLength]; - int read = 0; - while (read < _payloadLength) { - read += checkedRead(in.read(payload, read, _payloadLength - read)); - } - - if (isMasked()) { - for (int i = 0; i < payload.length; i++) { - payload[i] ^= maskingKey[i % 4]; - } - } - - //Test for Unicode errors - if (getOpCode() == OpCode.Text) { - _payloadString = binary2Text(getBinaryPayload()); - } - } - - public void write(OutputStream out) throws IOException { - byte header = 0; - if (fin) { - header |= 0x80; - } - header |= opCode.getValue() & 0x0F; - out.write(header); - - _payloadLength = getBinaryPayload().length; - if (_payloadLength <= 125) { - out.write(isMasked() ? 0x80 | (byte) _payloadLength : (byte) _payloadLength); - } else if (_payloadLength <= 0xFFFF) { - out.write(isMasked() ? 0xFE : 126); - out.write(_payloadLength >>> 8); - out.write(_payloadLength); - } else { - out.write(isMasked() ? 0xFF : 127); - out.write(_payloadLength >>> 56 & 0); //integer only contains 31 bit - out.write(_payloadLength >>> 48 & 0); - out.write(_payloadLength >>> 40 & 0); - out.write(_payloadLength >>> 32 & 0); - out.write(_payloadLength >>> 24); - out.write(_payloadLength >>> 16); - out.write(_payloadLength >>> 8); - out.write(_payloadLength); - } - - - if (isMasked()) { - out.write(maskingKey); - for (int i = 0; i < _payloadLength; i++) { - out.write(getBinaryPayload()[i] ^ maskingKey[i % 4]); - } - } else { - out.write(getBinaryPayload()); - } - out.flush(); - } - - // --------------------------------ENCODING-------------------------------- - - public static final Charset TEXT_CHARSET = Charset.forName("UTF-8"); - public static final CharsetDecoder TEXT_DECODER = TEXT_CHARSET.newDecoder(); - public static final CharsetEncoder TEXT_ENCODER = TEXT_CHARSET.newEncoder(); - - - public static String binary2Text(byte[] payload) throws CharacterCodingException { - return TEXT_DECODER.decode(ByteBuffer.wrap(payload)).toString(); - } - - public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException { - return TEXT_DECODER.decode(ByteBuffer.wrap(payload, offset, length)).toString(); - } - - public static byte[] text2Binary(String payload) throws CharacterCodingException { - return TEXT_ENCODER.encode(CharBuffer.wrap(payload)).array(); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("WS["); - sb.append(getOpCode()); - sb.append(", ").append(isFin() ? "fin" : "inter"); - sb.append(", ").append(isMasked() ? "masked" : "unmasked"); - sb.append(", ").append(payloadToString()); - sb.append(']'); - return sb.toString(); - } - - protected String payloadToString() { - if (payload == null) return "null"; - else { - final StringBuilder sb = new StringBuilder(); - sb.append('[').append(payload.length).append("b] "); - if (getOpCode() == OpCode.Text) { - String text = getTextPayload(); - if (text.length() > 100) - sb.append(text.substring(0, 100)).append("..."); - else - sb.append(text); - } else { - sb.append("0x"); - for (int i = 0; i < Math.min(payload.length, 50); ++i) - sb.append(Integer.toHexString((int) payload[i] & 0xFF)); - if (payload.length > 50) - sb.append("..."); - } - return sb.toString(); - } - } - - // --------------------------------CONSTANTS------------------------------- - - public static enum OpCode { - Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); - - private final byte code; - - private OpCode(int code) { - this.code = (byte) code; - } - - public byte getValue() { - return code; - } - - public boolean isControlFrame() { - return this == Close || this == Ping || this == Pong; - } - - public static OpCode find(byte value) { - for (OpCode opcode : values()) { - if (opcode.getValue() == value) { - return opcode; - } - } - return null; - } - } - - public static enum CloseCode { - NormalClosure(1000), GoingAway(1001), ProtocolError(1002), UnsupportedData(1003), NoStatusRcvd(1005), - AbnormalClosure(1006), InvalidFramePayloadData(1007), PolicyViolation(1008), MessageTooBig(1009), - MandatoryExt(1010), InternalServerError(1011), TLSHandshake(1015); - - private final int code; - - private CloseCode(int code) { - this.code = code; - } - - public int getValue() { - return code; - } - - public static CloseCode find(int value) { - for (CloseCode code : values()) { - if (code.getValue() == value) { - return code; - } - } - return null; - } - } - - // ------------------------------------------------------------------------ - - public static class CloseFrame extends WebSocketFrame { - private CloseCode _closeCode; - private String _closeReason; - - private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException { - super(wrap); - assert wrap.getOpCode() == OpCode.Close; - if (wrap.getBinaryPayload().length >= 2) { - _closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | - (wrap.getBinaryPayload()[1] & 0xFF)); - _closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); - } - } - - public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException { - super(OpCode.Close, true, generatePayload(code, closeReason)); - } - - private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException { - if (code != null) { - byte[] reasonBytes = text2Binary(closeReason); - byte[] payload = new byte[reasonBytes.length + 2]; - payload[0] = (byte) ((code.getValue() >> 8) & 0xFF); - payload[1] = (byte) ((code.getValue()) & 0xFF); - System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); - return payload; - } else { - return new byte[0]; - } - } - - protected String payloadToString() { - return (_closeCode != null ? _closeCode : "UnknownCloseCode[" + _closeCode + "]") + (_closeReason != null && !_closeReason.isEmpty() ? ": " + _closeReason : ""); - } - - public CloseCode getCloseCode() { - return _closeCode; - } - - public String getCloseReason() { - return _closeReason; - } - } -} |