我用 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/