All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.neovisionaries.ws.client.WebSocketFrame Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015-2016 Neo Visionaries Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.neovisionaries.ws.client;


import static com.neovisionaries.ws.client.WebSocketOpcode.BINARY;
import static com.neovisionaries.ws.client.WebSocketOpcode.CLOSE;
import static com.neovisionaries.ws.client.WebSocketOpcode.CONTINUATION;
import static com.neovisionaries.ws.client.WebSocketOpcode.PING;
import static com.neovisionaries.ws.client.WebSocketOpcode.PONG;
import static com.neovisionaries.ws.client.WebSocketOpcode.TEXT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * WebSocket frame.
 *
 * @see RFC 6455, 5. Data Framing
 */
public class WebSocketFrame
{
    private boolean mFin;
    private boolean mRsv1;
    private boolean mRsv2;
    private boolean mRsv3;
    private int mOpcode;
    private boolean mMask;
    private byte[] mPayload;


    /**
     * Get the value of FIN bit.
     *
     * @return
     *         The value of FIN bit.
     */
    public boolean getFin()
    {
        return mFin;
    }


    /**
     * Set the value of FIN bit.
     *
     * @param fin
     *         The value of FIN bit.
     *
     * @return
     *         {@code this} object.
     */
    public WebSocketFrame setFin(boolean fin)
    {
        mFin = fin;

        return this;
    }


    /**
     * Get the value of RSV1 bit.
     *
     * @return
     *         The value of RSV1 bit.
     */
    public boolean getRsv1()
    {
        return mRsv1;
    }


    /**
     * Set the value of RSV1 bit.
     *
     * @param rsv1
     *         The value of RSV1 bit.
     *
     * @return
     *         {@code this} object.
     */
    public WebSocketFrame setRsv1(boolean rsv1)
    {
        mRsv1 = rsv1;

        return this;
    }


    /**
     * Get the value of RSV2 bit.
     *
     * @return
     *         The value of RSV2 bit.
     */
    public boolean getRsv2()
    {
        return mRsv2;
    }


    /**
     * Set the value of RSV2 bit.
     *
     * @param rsv2
     *         The value of RSV2 bit.
     *
     * @return
     *         {@code this} object.
     */
    public WebSocketFrame setRsv2(boolean rsv2)
    {
        mRsv2 = rsv2;

        return this;
    }


    /**
     * Get the value of RSV3 bit.
     *
     * @return
     *         The value of RSV3 bit.
     */
    public boolean getRsv3()
    {
        return mRsv3;
    }


    /**
     * Set the value of RSV3 bit.
     *
     * @param rsv3
     *         The value of RSV3 bit.
     *
     * @return
     *         {@code this} object.
     */
    public WebSocketFrame setRsv3(boolean rsv3)
    {
        mRsv3 = rsv3;

        return this;
    }


    /**
     * Get the opcode.
     *
     * 
     *   
     *   
     *     
     *       
     *       
     *     
     *   
     *   
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *     
     *       
     *       
     *     
     *   
     * 
WebSocket opcode
ValueDescription
0x0Frame continuation
0x1Text frame
0x2Binary frame
0x3-0x7Reserved
0x8Connection close
0x9Ping
0xAPong
0xB-0xFReserved
* * @return * The opcode. * * @see WebSocketOpcode */ public int getOpcode() { return mOpcode; } /** * Set the opcode * * @param opcode * The opcode. * * @return * {@code this} object. * * @see WebSocketOpcode */ public WebSocketFrame setOpcode(int opcode) { mOpcode = opcode; return this; } /** * Check if this frame is a continuation frame. * *

* This method returns {@code true} when the value of the * opcode is 0x0 ({@link WebSocketOpcode#CONTINUATION}). *

* * @return * {@code true} if this frame is a continuation frame * (= if the opcode is 0x0). */ public boolean isContinuationFrame() { return (mOpcode == CONTINUATION); } /** * Check if this frame is a text frame. * *

* This method returns {@code true} when the value of the * opcode is 0x1 ({@link WebSocketOpcode#TEXT}). *

* * @return * {@code true} if this frame is a text frame * (= if the opcode is 0x1). */ public boolean isTextFrame() { return (mOpcode == TEXT); } /** * Check if this frame is a binary frame. * *

* This method returns {@code true} when the value of the * opcode is 0x2 ({@link WebSocketOpcode#BINARY}). *

* * @return * {@code true} if this frame is a binary frame * (= if the opcode is 0x2). */ public boolean isBinaryFrame() { return (mOpcode == BINARY); } /** * Check if this frame is a close frame. * *

* This method returns {@code true} when the value of the * opcode is 0x8 ({@link WebSocketOpcode#CLOSE}). *

* * @return * {@code true} if this frame is a close frame * (= if the opcode is 0x8). */ public boolean isCloseFrame() { return (mOpcode == CLOSE); } /** * Check if this frame is a ping frame. * *

* This method returns {@code true} when the value of the * opcode is 0x9 ({@link WebSocketOpcode#PING}). *

* * @return * {@code true} if this frame is a ping frame * (= if the opcode is 0x9). */ public boolean isPingFrame() { return (mOpcode == PING); } /** * Check if this frame is a pong frame. * *

* This method returns {@code true} when the value of the * opcode is 0xA ({@link WebSocketOpcode#PONG}). *

* * @return * {@code true} if this frame is a pong frame * (= if the opcode is 0xA). */ public boolean isPongFrame() { return (mOpcode == PONG); } /** * Check if this frame is a data frame. * *

* This method returns {@code true} when the value of the * opcode is in between 0x1 and 0x7. *

* * @return * {@code true} if this frame is a data frame * (= if the opcode is in between 0x1 and 0x7). */ public boolean isDataFrame() { return (0x1 <= mOpcode && mOpcode <= 0x7); } /** * Check if this frame is a control frame. * *

* This method returns {@code true} when the value of the * opcode is in between 0x8 and 0xF. *

* * @return * {@code true} if this frame is a control frame * (= if the opcode is in between 0x8 and 0xF). */ public boolean isControlFrame() { return (0x8 <= mOpcode && mOpcode <= 0xF); } /** * Get the value of MASK bit. * * @return * The value of MASK bit. */ boolean getMask() { return mMask; } /** * Set the value of MASK bit. * * @param mask * The value of MASK bit. * * @return * {@code this} object. */ WebSocketFrame setMask(boolean mask) { mMask = mask; return this; } /** * Check if this frame has payload. * * @return * {@code true} if this frame has payload. */ public boolean hasPayload() { return mPayload != null; } /** * Get the payload length. * * @return * The payload length. */ public int getPayloadLength() { if (mPayload == null) { return 0; } return mPayload.length; } /** * Get the unmasked payload. * * @return * The unmasked payload. {@code null} may be returned. */ public byte[] getPayload() { return mPayload; } /** * Get the unmasked payload as a text. * * @return * A string constructed by interrupting the payload * as a UTF-8 bytes. */ public String getPayloadText() { if (mPayload == null) { return null; } return Misc.toStringUTF8(mPayload); } /** * Set the unmasked payload. * *

* Note that the payload length of a control frame must be 125 bytes or less. *

* * @param payload * The unmasked payload. {@code null} is accepted. * An empty byte array is treated in the same way * as {@code null}. * * @return * {@code this} object. */ public WebSocketFrame setPayload(byte[] payload) { if (payload != null && payload.length == 0) { payload = null; } mPayload = payload; return this; } /** * Set the payload. The given string is converted to a byte array * in UTF-8 encoding. * *

* Note that the payload length of a control frame must be 125 bytes or less. *

* * @param payload * The unmasked payload. {@code null} is accepted. * An empty string is treated in the same way as * {@code null}. * * @return * {@code this} object. */ public WebSocketFrame setPayload(String payload) { if (payload == null || payload.length() == 0) { return setPayload((byte[])null); } return setPayload(Misc.getBytesUTF8(payload)); } /** * Set the payload that conforms to the payload format of close frames. * *

* The given parameters are encoded based on the rules described in * "5.5.1. Close" of RFC 6455. *

* *

* Note that the reason should not be too long because the payload * length of a control frame must be 125 bytes or less. *

* * @param closeCode * The close code. * * @param reason * The reason. {@code null} is accepted. An empty string * is treated in the same way as {@code null}. * * @return * {@code this} object. * * @see RFC 6455, 5.5.1. Close * * @see WebSocketCloseCode */ public WebSocketFrame setCloseFramePayload(int closeCode, String reason) { // Convert the close code to a 2-byte unsigned integer // in network byte order. byte[] encodedCloseCode = new byte[] { (byte)((closeCode >> 8) & 0xFF), (byte)((closeCode ) & 0xFF) }; // If a reason string is not given. if (reason == null || reason.length() == 0) { // Use the close code only. return setPayload(encodedCloseCode); } // Convert the reason into a byte array. byte[] encodedReason = Misc.getBytesUTF8(reason); // Concatenate the close code and the reason. byte[] payload = new byte[2 + encodedReason.length]; System.arraycopy(encodedCloseCode, 0, payload, 0, 2); System.arraycopy(encodedReason, 0, payload, 2, encodedReason.length); // Use the concatenated string. return setPayload(payload); } /** * Parse the first two bytes of the payload as a close code. * *

* If any payload is not set or the length of the payload is less than 2, * this method returns 1005 ({@link WebSocketCloseCode#NONE}). *

* *

* The value returned from this method is meaningless if this frame * is not a close frame. *

* * @return * The close code. * * @see RFC 6455, 5.5.1. Close * * @see WebSocketCloseCode */ public int getCloseCode() { if (mPayload == null || mPayload.length < 2) { return WebSocketCloseCode.NONE; } // A close code is encoded in network byte order. int closeCode = (((mPayload[0] & 0xFF) << 8) | (mPayload[1] & 0xFF)); return closeCode; } /** * Parse the third and subsequent bytes of the payload as a close reason. * *

* If any payload is not set or the length of the payload is less than 3, * this method returns {@code null}. *

* *

* The value returned from this method is meaningless if this frame * is not a close frame. *

* * @return * The close reason. */ public String getCloseReason() { if (mPayload == null || mPayload.length < 3) { return null; } return Misc.toStringUTF8(mPayload, 2, mPayload.length - 2); } @Override public String toString() { StringBuilder builder = new StringBuilder() .append("WebSocketFrame(FIN=").append(mFin ? "1" : "0") .append(",RSV1=").append(mRsv1 ? "1" : "0") .append(",RSV2=").append(mRsv2 ? "1" : "0") .append(",RSV3=").append(mRsv3 ? "1" : "0") .append(",Opcode=").append(Misc.toOpcodeName(mOpcode)) .append(",Length=").append(getPayloadLength()); switch (mOpcode) { case TEXT: appendPayloadText(builder); break; case BINARY: appendPayloadBinary(builder); break; case CLOSE: appendPayloadClose(builder); break; } return builder.append(")").toString(); } private boolean appendPayloadCommon(StringBuilder builder) { builder.append(",Payload="); if (mPayload == null) { builder.append("null"); // Nothing more to append. return true; } if (mRsv1) { // In the current implementation, mRsv1=true is allowed // only when Per-Message Compression is applied. builder.append("compressed"); // Nothing more to append. return true; } // Continue. return false; } private void appendPayloadText(StringBuilder builder) { if (appendPayloadCommon(builder)) { // Nothing more to append. return; } builder.append("\""); builder.append(getPayloadText()); builder.append("\""); } private void appendPayloadClose(StringBuilder builder) { builder .append(",CloseCode=").append(getCloseCode()) .append(",Reason="); String reason = getCloseReason(); if (reason == null) { builder.append("null"); } else { builder.append("\"").append(reason).append("\""); } } private void appendPayloadBinary(StringBuilder builder) { if (appendPayloadCommon(builder)) { // Nothing more to append. return; } for (int i = 0; i < mPayload.length; ++i) { builder.append(String.format("%02X ", (0xFF & mPayload[i]))); } if (mPayload.length != 0) { // Remove the last space. builder.setLength(builder.length() - 1); } } /** * Create a continuation frame. Note that the FIN bit of the * returned frame is false. * * @return * A WebSocket frame whose FIN bit is false, opcode is * {@link WebSocketOpcode#CONTINUATION CONTINUATION} and * payload is {@code null}. */ public static WebSocketFrame createContinuationFrame() { return new WebSocketFrame() .setOpcode(CONTINUATION); } /** * Create a continuation frame. Note that the FIN bit of the * returned frame is false. * * @param payload * The payload for a newly create frame. * * @return * A WebSocket frame whose FIN bit is false, opcode is * {@link WebSocketOpcode#CONTINUATION CONTINUATION} and * payload is the given one. */ public static WebSocketFrame createContinuationFrame(byte[] payload) { return createContinuationFrame().setPayload(payload); } /** * Create a continuation frame. Note that the FIN bit of the * returned frame is false. * * @param payload * The payload for a newly create frame. * * @return * A WebSocket frame whose FIN bit is false, opcode is * {@link WebSocketOpcode#CONTINUATION CONTINUATION} and * payload is the given one. */ public static WebSocketFrame createContinuationFrame(String payload) { return createContinuationFrame().setPayload(payload); } /** * Create a text frame. * * @param payload * The payload for a newly created frame. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#TEXT TEXT} and payload is * the given one. */ public static WebSocketFrame createTextFrame(String payload) { return new WebSocketFrame() .setFin(true) .setOpcode(TEXT) .setPayload(payload); } /** * Create a binary frame. * * @param payload * The payload for a newly created frame. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#BINARY BINARY} and payload is * the given one. */ public static WebSocketFrame createBinaryFrame(byte[] payload) { return new WebSocketFrame() .setFin(true) .setOpcode(BINARY) .setPayload(payload); } /** * Create a close frame. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#CLOSE CLOSE} and payload is * {@code null}. */ public static WebSocketFrame createCloseFrame() { return new WebSocketFrame() .setFin(true) .setOpcode(CLOSE); } /** * Create a close frame. * * @param closeCode * The close code. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#CLOSE CLOSE} and payload * contains a close code. * * @see WebSocketCloseCode */ public static WebSocketFrame createCloseFrame(int closeCode) { return createCloseFrame().setCloseFramePayload(closeCode, null); } /** * Create a close frame. * * @param closeCode * The close code. * * @param reason * The close reason. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, 5.5. Control Frames). * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#CLOSE CLOSE} and payload * contains a close code and a close reason. * * @see WebSocketCloseCode */ public static WebSocketFrame createCloseFrame(int closeCode, String reason) { return createCloseFrame().setCloseFramePayload(closeCode, reason); } /** * Create a ping frame. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PING PING} and payload is * {@code null}. */ public static WebSocketFrame createPingFrame() { return new WebSocketFrame() .setFin(true) .setOpcode(PING); } /** * Create a ping frame. * * @param payload * The payload for a newly created frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, 5.5. Control Frames). * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PING PING} and payload is * the given one. */ public static WebSocketFrame createPingFrame(byte[] payload) { return createPingFrame().setPayload(payload); } /** * Create a ping frame. * * @param payload * The payload for a newly created frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, 5.5. Control Frames). * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PING PING} and payload is * the given one. */ public static WebSocketFrame createPingFrame(String payload) { return createPingFrame().setPayload(payload); } /** * Create a pong frame. * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PONG PONG} and payload is * {@code null}. */ public static WebSocketFrame createPongFrame() { return new WebSocketFrame() .setFin(true) .setOpcode(PONG); } /** * Create a pong frame. * * @param payload * The payload for a newly created frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, 5.5. Control Frames). * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PONG PONG} and payload is * the given one. */ public static WebSocketFrame createPongFrame(byte[] payload) { return createPongFrame().setPayload(payload); } /** * Create a pong frame. * * @param payload * The payload for a newly created frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, 5.5. Control Frames). * * @return * A WebSocket frame whose FIN bit is true, opcode is * {@link WebSocketOpcode#PONG PONG} and payload is * the given one. */ public static WebSocketFrame createPongFrame(String payload) { return createPongFrame().setPayload(payload); } /** * Mask/unmask payload. * *

* The logic of masking/unmasking is described in "5.3. * Client-to-Server Masking" in RFC 6455. *

* * @param maskingKey * The masking key. If {@code null} is given or the length * of the masking key is less than 4, nothing is performed. * * @param payload * Payload to be masked/unmasked. * * @return * {@code payload}. * * @see 5.3. Client-to-Server Masking */ static byte[] mask(byte[] maskingKey, byte[] payload) { if (maskingKey == null || maskingKey.length < 4 || payload == null) { return payload; } for (int i = 0; i < payload.length; ++i) { payload[i] ^= maskingKey[i % 4]; } return payload; } static WebSocketFrame compressFrame(WebSocketFrame frame, PerMessageCompressionExtension pmce) { // If Per-Message Compression is not enabled. if (pmce == null) { // No compression. return frame; } // If the frame is neither a TEXT frame nor a BINARY frame. if (frame.isTextFrame() == false && frame.isBinaryFrame() == false) { // No compression. return frame; } // If the frame is not the final frame. if (frame.getFin() == false) { // The compression must be applied to this frame and // all the subsequent continuation frames, but the // current implementation does not support the behavior. return frame; } // If the RSV1 bit is set. if (frame.getRsv1()) { // In the current implementation, RSV1=true is allowed // only as Per-Message Compressed Bit (See RFC 7692, // 6. Framing). Therefore, RSV1=true here is regarded // as "already compressed". return frame; } // The plain payload before compression. byte[] payload = frame.getPayload(); // If the payload is empty. if (payload == null || payload.length == 0) { // No compression. return frame; } // Compress the payload. byte[] compressed = compress(payload, pmce); // If the length of the compressed data is not less than // that of the original plain payload. if (payload.length <= compressed.length) { // It's better not to compress the payload. return frame; } // Replace the plain payload with the compressed data. frame.setPayload(compressed); // Set Per-Message Compressed Bit (See RFC 7692, 6. Framing). frame.setRsv1(true); return frame; } private static byte[] compress(byte[] data, PerMessageCompressionExtension pmce) { try { // Compress the data. return pmce.compress(data); } catch (WebSocketException e) { // Failed to compress the data. Ignore this error and use // the plain original data. The current implementation // does not call any listener callback method for this error. return data; } } static List splitIfNecessary( WebSocketFrame frame, int maxPayloadSize, PerMessageCompressionExtension pmce) { // If the maximum payload size is not specified. if (maxPayloadSize == 0) { // Not split. return null; } // If the total length of the payload is equal to or // less than the maximum payload size. if (frame.getPayloadLength() <= maxPayloadSize) { // Not split. return null; } // If the frame is a binary frame or a text frame. if (frame.isBinaryFrame() || frame.isTextFrame()) { // Try to compress the frame. In the current implementation, binary // frames and text frames with the FIN bit true can be compressed. // The compressFrame() method may change the payload and the RSV1 // bit of the given frame. frame = compressFrame(frame, pmce); // If the payload length of the frame has become equal to or less // than the maximum payload size as a result of the compression. if (frame.getPayloadLength() <= maxPayloadSize) { // Not split. (Note that the frame has been compressed) return null; } } else if (frame.isContinuationFrame() == false) { // Control frames (Close/Ping/Pong) are not split. return null; } // Split the frame. return split(frame, maxPayloadSize); } private static List split(WebSocketFrame frame, int maxPayloadSize) { // The original payload and the original FIN bit. byte[] originalPayload = frame.getPayload(); boolean originalFin = frame.getFin(); List frames = new ArrayList(); // Generate the first frame using the existing WebSocketFrame instance. // Note that the reserved bit 1 and the opcode are untouched. byte[] payload = Arrays.copyOf(originalPayload, maxPayloadSize); frame.setFin(false).setPayload(payload); frames.add(frame); for (int from = maxPayloadSize; from < originalPayload.length; from += maxPayloadSize) { // Prepare the payload of the next continuation frame. int to = Math.min(from + maxPayloadSize, originalPayload.length); payload = Arrays.copyOfRange(originalPayload, from, to); // Create a continuation frame. WebSocketFrame cont = WebSocketFrame.createContinuationFrame(payload); frames.add(cont); } if (originalFin) { // Set the FIN bit of the last frame. frames.get(frames.size() - 1).setFin(true); } return frames; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy