// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.websocket.common; import java.nio.ByteBuffer; import java.util.Arrays; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.frames.BinaryFrame; import org.eclipse.jetty.websocket.common.frames.CloseFrame; import org.eclipse.jetty.websocket.common.frames.ContinuationFrame; import org.eclipse.jetty.websocket.common.frames.PingFrame; import org.eclipse.jetty.websocket.common.frames.PongFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; /** * A Base Frame as seen in <a href="https://tools.ietf.org/html/rfc6455#section-5.2">RFC 6455. Sec 5.2</a> * * <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 * +-+-+-+-+-------+-+-------------+-------------------------------+ * |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 ... | * +---------------------------------------------------------------+ * </pre> */ 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. * * <pre> * 1000_0000 (0x80) = fin * 0100_0000 (0x40) = rsv1 * 0010_0000 (0x20) = rsv2 * 0001_0000 (0x10) = rsv3 * 0000_1111 (0x0F) = opcode * </pre> */ protected byte finRsvOp; protected boolean masked = false; protected byte mask[]; /** * The payload data. * <p> * 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; } if (masked != other.masked) { return false; } return true; } @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 BufferUtil.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 isLast() { return isFin(); } @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. * <p> * The provided buffer will be used as is, no copying of bytes performed. * <p> * 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(); } }