com.fireflysource.net.websocket.common.frame.WebSocketFrame Maven / Gradle / Ivy
package com.fireflysource.net.websocket.common.frame;
import com.fireflysource.common.io.BufferUtils;
import com.fireflysource.net.websocket.common.model.OpCode;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* A Base Frame as seen in RFC 6455. Sec 5.2
*
*
* 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
* +-+-+-+-+-------+-+-------------+-------------------------------+
* |F|R|R|R| opcode|M| Payload len | Extended payload length |
* |I|S|S|S| (4) |A| (7) | (16/64) |
* |N|V|V|V| |S| | (if payload len==126/127) |
* | |1|2|3| |K| | |
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
* | Extended payload length continued, if payload len == 127 |
* + - - - - - - - - - - - - - - - +-------------------------------+
* | |Masking-key, if MASK set to 1 |
* +-------------------------------+-------------------------------+
* | Masking-key (continued) | Payload Data |
* +-------------------------------- - - - - - - - - - - - - - - - +
* : Payload Data continued ... :
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | Payload Data continued ... |
* +---------------------------------------------------------------+
*
*/
public abstract class WebSocketFrame implements Frame {
public static WebSocketFrame copy(Frame original) {
WebSocketFrame copy;
switch (original.getOpCode()) {
case OpCode.BINARY:
copy = new BinaryFrame();
break;
case OpCode.TEXT:
copy = new TextFrame();
break;
case OpCode.CLOSE:
copy = new CloseFrame();
break;
case OpCode.CONTINUATION:
copy = new ContinuationFrame();
break;
case OpCode.PING:
copy = new PingFrame();
break;
case OpCode.PONG:
copy = new PongFrame();
break;
default:
throw new IllegalArgumentException("Cannot copy frame with opcode " + original.getOpCode() + " - " + original);
}
copy.copyHeaders(original);
ByteBuffer payload = original.getPayload();
if (payload != null) {
ByteBuffer payloadCopy = ByteBuffer.allocate(payload.remaining());
payloadCopy.put(payload.slice()).flip();
copy.setPayload(payloadCopy);
}
return copy;
}
/**
* Combined FIN + RSV1 + RSV2 + RSV3 + OpCode byte.
*
*
* 1000_0000 (0x80) = fin
* 0100_0000 (0x40) = rsv1
* 0010_0000 (0x20) = rsv2
* 0001_0000 (0x10) = rsv3
* 0000_1111 (0x0F) = opcode
*
*/
protected byte finRsvOp;
protected boolean masked = false;
protected byte[] mask;
/**
* The payload data.
*
* It is assumed to always be in FLUSH mode (ready to read) in this object.
*/
protected ByteBuffer data;
/**
* Construct form opcode
*
* @param opcode the opcode the frame is based on
*/
protected WebSocketFrame(byte opcode) {
reset();
setOpCode(opcode);
}
public abstract void assertValid();
protected void copyHeaders(Frame frame) {
finRsvOp = 0x00;
finRsvOp |= frame.isFin() ? 0x80 : 0x00;
finRsvOp |= frame.isRsv1() ? 0x40 : 0x00;
finRsvOp |= frame.isRsv2() ? 0x20 : 0x00;
finRsvOp |= frame.isRsv3() ? 0x10 : 0x00;
finRsvOp |= frame.getOpCode() & 0x0F;
masked = frame.isMasked();
if (masked) {
mask = frame.getMask();
} else {
mask = null;
}
}
protected void copyHeaders(WebSocketFrame copy) {
finRsvOp = copy.finRsvOp;
masked = copy.masked;
mask = null;
if (copy.mask != null)
mask = Arrays.copyOf(copy.mask, copy.mask.length);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
WebSocketFrame other = (WebSocketFrame) obj;
if (data == null) {
if (other.data != null) {
return false;
}
} else if (!data.equals(other.data)) {
return false;
}
if (finRsvOp != other.finRsvOp) {
return false;
}
if (!Arrays.equals(mask, other.mask)) {
return false;
}
return masked == other.masked;
}
@Override
public byte[] getMask() {
return mask;
}
@Override
public final byte getOpCode() {
return (byte) (finRsvOp & 0x0F);
}
/**
* Get the payload ByteBuffer. possible null.
*/
@Override
public ByteBuffer getPayload() {
return data;
}
public String getPayloadAsUTF8() {
return BufferUtils.toUTF8String(getPayload());
}
@Override
public int getPayloadLength() {
if (data == null) {
return 0;
}
return data.remaining();
}
@Override
public Type getType() {
return Type.from(getOpCode());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + ((data == null) ? 0 : data.hashCode());
result = (prime * result) + finRsvOp;
result = (prime * result) + Arrays.hashCode(mask);
return result;
}
@Override
public boolean hasPayload() {
return ((data != null) && data.hasRemaining());
}
public abstract boolean isControlFrame();
public abstract boolean isDataFrame();
@Override
public boolean isFin() {
return (byte) (finRsvOp & 0x80) != 0;
}
@Override
public boolean isMasked() {
return masked;
}
@Override
public boolean isRsv1() {
return (byte) (finRsvOp & 0x40) != 0;
}
@Override
public boolean isRsv2() {
return (byte) (finRsvOp & 0x20) != 0;
}
@Override
public boolean isRsv3() {
return (byte) (finRsvOp & 0x10) != 0;
}
public void reset() {
finRsvOp = (byte) 0x80; // FIN (!RSV, opcode 0)
masked = false;
data = null;
mask = null;
}
public WebSocketFrame setFin(boolean fin) {
// set bit 1
this.finRsvOp = (byte) ((finRsvOp & 0x7F) | (fin ? 0x80 : 0x00));
return this;
}
public Frame setMask(byte[] maskingKey) {
this.mask = maskingKey;
this.masked = (mask != null);
return this;
}
public Frame setMasked(boolean mask) {
this.masked = mask;
return this;
}
protected WebSocketFrame setOpCode(byte op) {
this.finRsvOp = (byte) ((finRsvOp & 0xF0) | (op & 0x0F));
return this;
}
/**
* Set the data payload.
*
* The provided buffer will be used as is, no copying of bytes performed.
*
* The provided buffer should be flipped and ready to READ from.
*
* @param buf the bytebuffer to set
* @return the frame itself
*/
public WebSocketFrame setPayload(ByteBuffer buf) {
data = buf;
return this;
}
public WebSocketFrame setRsv1(boolean rsv1) {
// set bit 2
this.finRsvOp = (byte) ((finRsvOp & 0xBF) | (rsv1 ? 0x40 : 0x00));
return this;
}
public WebSocketFrame setRsv2(boolean rsv2) {
// set bit 3
this.finRsvOp = (byte) ((finRsvOp & 0xDF) | (rsv2 ? 0x20 : 0x00));
return this;
}
public WebSocketFrame setRsv3(boolean rsv3) {
// set bit 4
this.finRsvOp = (byte) ((finRsvOp & 0xEF) | (rsv3 ? 0x10 : 0x00));
return this;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(OpCode.name((byte) (finRsvOp & 0x0F)));
b.append('[');
b.append("len=").append(getPayloadLength());
b.append(",fin=").append((finRsvOp & 0x80) != 0);
b.append(",rsv=");
b.append(((finRsvOp & 0x40) != 0) ? '1' : '.');
b.append(((finRsvOp & 0x20) != 0) ? '1' : '.');
b.append(((finRsvOp & 0x10) != 0) ? '1' : '.');
b.append(",masked=").append(masked);
b.append(']');
return b.toString();
}
}