java - 如何在 Java 中解析和验证 WebSocket 框架?

标签 java websocket frame

我用 Java 写了一个 WebSocket 帧解码器:

private byte[] decodeFrame(byte[] _rawIn) {
        int maskIndex = 2;
        byte[] maskBytes = new byte[4];

        if ((_rawIn[1] & (byte) 127) == 126) {
            maskIndex = 4;
        } else if ((_rawIn[1] & (byte) 127) == 127) {
            maskIndex = 10;
        }

        System.arraycopy(_rawIn, maskIndex, maskBytes, 0, 4);

        byte[] message = new byte[_rawIn.length - maskIndex - 4];

        for (int i = maskIndex + 4; i < _rawIn.length; i++) {
            message[i - maskIndex - 4] = (byte) (_rawIn[i] ^ maskBytes[(i - maskIndex - 4) % 4]);
        }

        return message;
    }

它有效,但我不知道如何验证帧以确保它只解码有效帧。

不幸的是,协议(protocol)描述 http://tools.ietf.org/html/rfc6455 并没有说明太多关​​于帧验证的信息。

最佳答案

解析原始 websocket 框架非常简单。 但是您必须一次检查一个字节的 header 。

这是一个粗略的例子:

我留下了一些 TODO 供您自行解决(当然是在阅读了 RFC-6455 规范之后)

您可以验证的事情:

Base Framing Protocol: RFC-6455 - Section 5.2

  • 操作码是否是规范中定义的有效操作码之一?
  • RSV 位是否使用不当?

Client-to-Server Masking: RFC 6455 - Section 5.3

  • 如果 Frame 是由 Client 发送的,是否屏蔽了 Frame?
  • Mask 是否在帧与帧之间是随机的?
  • 不允许 [0x00, 0x00, 0x00, 0x00] 作为掩码。

Fragmentation: RFC 6455 - Section 5.4

  • 它是一个支离 splinter 的控制框架吗?
  • 由多个帧组成的大消息的碎片是否乱序?
  • 新消息是否在前一个消息完成之前开始并带有 FIN 标志?

Control Frames: RFC 6455 - Section 5.5

  • 控制帧的负载长度是否超过 125 字节?
  • 负载是否碎片化?

Close Frames: RFC 6455 - Section 5.5.1

  • 如果有效载荷中提供了状态代码,该状态代码是否符合 section 7.4.1 中声明的状态代码之一? ?不要忘记检查 IANA registry of websocket status codes在 RFC 定稿后添加)
  • 状态码是否允许在帧中通过网络发送? (例如,参见代码 1005 和 1006)
  • 如果在框架中提供/reason/,是否符合UTF-8编码规则?
  • 在关闭帧之后,您是否收到过任何类型的帧? (这是一个禁忌)

Data Frames: RFC 6455 - Section 5.6

  • 如果您收到 TEXT 负载数据(来自 TEXT + CONTINUATION 帧),负载数据是否符合 UTF-8 编码规则?

虽然您可以在单个框架级别进行验证,但您会发现上面的一些验证是对多个框架之间的状态和行为的验证。您可以在 Sending and Receiving Data: RFC 6455 - Section 6 中找到更多此类验证。 .

但是,如果您在组合中有扩展,那么您还需要从协商的扩展堆栈的角度来处理帧。 当使用扩展时,上面的一些测试似乎无效。

示例:您有 Compression Extension (RFC-7692) (例如 permessage-deflate),那么 TEXT 有效负载的验证无法通过网络上的原始帧完成,因为您必须首先通过扩展传递帧。请注意,扩展可以更改碎片以满足其需要,这也可能会扰乱您的验证。

package websocket;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

public class RawParse
{
    public static class Frame
    {
        byte opcode;
        boolean fin;
        byte payload[];
    }

    public static Frame parse(byte raw[])
    {
        // easier to do this via ByteBuffer
        ByteBuffer buf = ByteBuffer.wrap(raw);

        // Fin + RSV + OpCode byte
        Frame frame = new Frame();
        byte b = buf.get();
        frame.fin = ((b & 0x80) != 0);
        boolean rsv1 = ((b & 0x40) != 0);
        boolean rsv2 = ((b & 0x20) != 0);
        boolean rsv3 = ((b & 0x10) != 0);
        frame.opcode = (byte)(b & 0x0F);

        // TODO: add control frame fin validation here
        // TODO: add frame RSV validation here

        // Masked + Payload Length
        b = buf.get();
        boolean masked = ((b & 0x80) != 0);
        int payloadLength = (byte)(0x7F & b);
        int byteCount = 0;
        if (payloadLength == 0x7F)
        {
            // 8 byte extended payload length
            byteCount = 8;
        }
        else if (payloadLength == 0x7E)
        {
            // 2 bytes extended payload length
            byteCount = 2;
        }

        // Decode Payload Length
        while (--byteCount > 0)
        {
            b = buf.get();
            payloadLength |= (b & 0xFF) << (8 * byteCount);
        }
        
        // TODO: add control frame payload length validation here

        byte maskingKey[] = null;
        if (masked)
        {
            // Masking Key
            maskingKey = new byte[4];
            buf.get(maskingKey,0,4);
        }
        
        // TODO: add masked + maskingkey validation here

        // Payload itself
        frame.payload = new byte[payloadLength];
        buf.get(frame.payload,0,payloadLength);

        // Demask (if needed)
        if (masked)
        {
            for (int i = 0; i < frame.payload.length; i++)
            {
                frame.payload[i] ^= maskingKey[i % 4];
            }
        }

        return frame;
    }

    public static void main(String[] args)
    {
        Charset UTF8 = Charset.forName("UTF-8");

        Frame closeFrame = parse(hexToByteArray("8800"));
        System.out.printf("closeFrame.opcode = %d%n",closeFrame.opcode);
        System.out.printf("closeFrame.payload.length = %d%n",closeFrame.payload.length);

        // Examples from https://www.rfc-editor.org/rfc/rfc6455#section-5.7
        Frame unmaskedTextFrame = parse(hexToByteArray("810548656c6c6f"));
        System.out.printf("unmaskedTextFrame.opcode = %d%n",unmaskedTextFrame.opcode);
        System.out.printf("unmaskedTextFrame.payload.length = %d%n",unmaskedTextFrame.payload.length);
        System.out.printf("unmaskedTextFrame.payload = \"%s\"%n",new String(unmaskedTextFrame.payload,UTF8));

        Frame maskedTextFrame = parse(hexToByteArray("818537fa213d7f9f4d5158"));
        System.out.printf("maskedTextFrame.opcode = %d%n",maskedTextFrame.opcode);
        System.out.printf("maskedTextFrame.payload.length = %d%n",maskedTextFrame.payload.length);
        System.out.printf("maskedTextFrame.payload = \"%s\"%n",new String(maskedTextFrame.payload,UTF8));
    }

    public static byte[] hexToByteArray(String hstr)
    {
        if ((hstr.length() < 0) || ((hstr.length() % 2) != 0))
        {
            throw new IllegalArgumentException(String.format("Invalid string length of <%d>",hstr.length()));
        }

        int size = hstr.length() / 2;
        byte buf[] = new byte[size];
        byte hex;
        int len = hstr.length();

        int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
        for (int i = 0; i < len; i++)
        {
            hex = 0;
            if (i >= 0)
            {
                hex = (byte)(Character.digit(hstr.charAt(i),16) << 4);
            }
            i++;
            hex += (byte)(Character.digit(hstr.charAt(i),16));

            buf[idx] = hex;
            idx++;
        }

        return buf;
    }
}

关于java - 如何在 Java 中解析和验证 WebSocket 框架?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18368130/

相关文章:

javascript - 使用 Greasemonkey 脚本在框架中调用函数

java - 如何使用 JDBC 对数据库目录和模式进行分组?

java - 访问由 Spring 的类转换器创建的对象上的 getter 时出现 ArrayIndexOutOfBoundsException

javascript - 如何在 sockjs-STOMP 上禁用调试消息

php - Websocket:如何在 C 中编码文本以发送给客户端

javascript - 如何在 Kinetic.js 中用 Canvas 图像制作动画?

ios - 如何在滚动 contentView 之前向上移动 ScrollView 的框架? (当scrollView从屏幕中间开始时)

java - 多文件读取循环并区分 .pdf 和 .doc 文件

java - JSTL负零

php - 如何访问 Ratchet php 周期性循环和客户端在应用程序内发送?