websocket服务器端对于websocket协议中FIN 位、操作码、Mask 位、长度字段、Payload 复制数据到 Buffer 中的测试

//先直接上代码再解说

let encodeMessage = function(opcode, payload) {
    let buf;
    let b1 = 0x80 | opcode; // FIN 位(0x80) + 操作码(如 TEXT: 1 → 0x81)
    let b2 = 0;             // Mask 位为 0(服务器→客户端无需掩码)
    let length = payload.length;

    if (length < 126) { // 短消息:长度直接写入 b2
        buf = Buffer.alloc(payload.length + 2); // 头部 2 字节
        b2 |= length;                           // 设置长度字段
        buf.writeUInt8(b1, 0);                  // 写入 FIN + 操作码
        buf.writeUInt8(b2, 1);                 // 写入 Mask 位 + 长度
        payload.copy(buf, 2);                   // 复制 Payload

    } else if (length < (1 << 16)) { // 中等消息:长度占 2 字节,(1<<16)就是65535
        buf = Buffer.alloc(payload.length + 4); // 头部 4 字节(2 + 2)
        b2 |= 126;                             // 长度标识符 126
        buf.writeUInt8(b1, 0);
        buf.writeUInt8(b2, 1);
        buf.writeUInt16BE(length, 2);          // 写入 16 位大端长度
        payload.copy(buf, 4);                  // 复制 Payload

    } else { // 长消息:长度占 8 字节
        buf = Buffer.alloc(payload.length + 10); // 头部 10 字节(2 + 8)
        b2 |= 127;                              // 长度标识符 127
        buf.writeUInt8(b1, 0);
        buf.writeUInt8(b2, 1);
        buf.writeUInt32BE(0, 2);                // 高 32 位(假设长度 ≤ 2^32-1)
        buf.writeUInt32BE(length, 6);           // 低 32 位
        payload.copy(buf, 10);                  // 复制 Payload
    }

    return buf;
};


const assert = require('assert');
const opcodes = { TEXT: 1, BINARY: 2, CLOSE: 8, PING: 9, PONG: 10 };

// 测试短消息(长度 < 126)
function testShortMessage() {
    const payload = Buffer.from('Hello');
    const buf = encodeMessage(opcodes.TEXT, payload);

    // 验证头部
    assert.strictEqual(buf.readUInt8(0), 0x80 | opcodes.TEXT); // FIN + TEXT opcode
    assert.strictEqual(buf.readUInt8(1), payload.length);      // 长度字段
    // 验证 Payload
    assert.deepStrictEqual(buf.slice(2), payload);
}

// 测试中等消息(长度 = 126)
function testMediumMessage() {
    const payload = Buffer.alloc(126);
    const buf = encodeMessage(opcodes.BINARY, payload);

    assert.strictEqual(buf.readUInt8(0), 0x80 | opcodes.BINARY);
    assert.strictEqual(buf.readUInt8(1), 126);                 // 长度标识符 126
    assert.strictEqual(buf.readUInt16BE(2), 126);              // 实际长度
    assert.deepStrictEqual(buf.slice(4), payload);
}

// 测试长消息(长度 > 65535)
function testLongMessage() {
    const length = 70000;
    const payload = Buffer.alloc(length);
    const buf = encodeMessage(opcodes.PING, payload);

    assert.strictEqual(buf.readUInt8(0), 0x80 | opcodes.PING);
    assert.strictEqual(buf.readUInt8(1), 127);                 // 长度标识符 127
    assert.strictEqual(buf.readUInt32BE(2), 0);                // 高 32 位(假设长度 ≤ 2^32-1)
    assert.strictEqual(buf.readUInt32BE(6), length);           // 低 32 位
    assert.deepStrictEqual(buf.slice(10), payload);
}

// 执行测试
testShortMessage();
testMediumMessage();
testLongMessage();
console.log('All tests passed!');

首先,函数定义了一个opcodes对象,包含不同的操作码,比如TEXT是1,BINARY是2等等。然后,encodeMessage函数根据传入的opcode和payload生成一个Buffer。根据payload的长度不同,数据帧的格式也不同:如果长度小于126字节,使用一个字节表示长度;如果在126到65535之间,则用接下来的两个字节表示长度;如果更大,则用接下来的八个字节。同时,第一个字节的最高位(FIN位)设置为0x80,表示这是消息的最后一个帧,后面跟着操作码。第二个字节的最高位是掩码位,这里设置为0,表示数据未掩码,后面跟着长度字段。

接下来,我需要考虑如何测试这个函数是否正确处理不同长度的payload,以及是否正确设置各个字段。可能的测试用例包括:

  1. 测试一个长度小于126的payload,比如长度为0,或者125的情况,检查生成的Buffer是否正确。

  2. 测试长度恰好为126的情况,这时候应该使用两个字节表示长度。

  3. 测试长度大于等于126但小于等于65535的情况,比如127或65535,这时候应使用两个字节的长度。

  4. 测试长度超过65535的情况,这时候应使用八个字节的长度,并且前四个字节可能为0(因为JavaScript的Buffer最大长度可能受限制,但假设这里处理的是大端序的64位长度)。

  5. 检查操作码是否正确设置,比如传入不同的opcode时,第一个字节的低四位是否正确。

  6. 确保mask位是0,因为服务器发送的数据不需要掩码,客户端发送的才需要。所以第二个字节的最高位是否为0。

  7. 检查payload是否正确复制到Buffer的指定位置。

现在,我需要针对这些情况编写测试代码,可能使用Node.js的assert模块或者其他测试框架如Mocha。例如,对于长度小于126的情况,可以生成一个短字符串作为payload,调用encodeMessage,然后检查返回的Buffer的长度是否正确,各个字段是否正确。

现在逐行分析代码:

let encodeMessage = function(opcode, payload) {

let buf;

let b1 = 0x80 | opcode; // 设置FIN位为1(0x80),操作码为传入的opcode

let b2 = 0; // 初始第二个字节,mask位为0,长度部分初始为0

let length = payload.length;

// 根据长度决定使用哪种格式

if (length < 126) {

// 长度字段占1字节

buf = Buffer.alloc(payload.length + 2 + 0); // 总长度是payload长度+2字节头部

b2 |= length; // 设置长度字段

buf.writeUInt8(b1, 0); // 写入第一个字节

buf.writeUInt8(b2, 1); // 写入第二个字节

payload.copy(buf, 2); // 将payload复制到Buffer的第3个字节开始的位置

} else if (length < (1 << 16)) { // 检查是否小于65536

// 长度字段占2字节

buf = Buffer.alloc(payload.length + 2 + 2); // 额外两个字节用于长度

b2 |= 126; // 设置第二个字节的长度字段为126

buf.writeUInt8(b1, 0);

buf.writeUInt8(b2, 1);

buf.writeUInt16BE(length, 2); // 大端序写入两个字节的长度

payload.copy(buf, 4); // 从第5个字节开始复制payload

} else {

// 长度字段占8字节

buf = Buffer.alloc(payload.length + 2 + 8); // 额外8字节用于长度

b2 |= 127; // 设置第二个字节的长度字段为127

buf.writeUInt8(b1, 0);

buf.writeUInt8(b2, 1);

// 写入8字节长度,前四个字节为0,后四个字节为实际长度(假设长度不超过32位)

buf.writeUInt32BE(0, 2); // 写入前四个字节为0

buf.writeUInt32BE(length, 6); // 后四个字节为长度的大端序

payload.copy(buf, 10); // 从第11个字节开始复制payload

}

return buf;

};

需要注意的是,对于超过32位的情况,代码可能存在错误。因为如果payload的长度超过2^32-1,这里只能写入后四个字节,前四个字节被写为0,这会导致无法处理非常大的数据。但实际应用中可能这种情况很少,或者受限于Buffer的最大容量。

测试这个函数时,需要覆盖这三种情况:

测试用例1:长度小于126

例如,payload长度为5,opcode为opcodes.TEXT(1)。生成的Buffer前两个字节应该是0x81(0x80 | 1)和0x05。后面跟着5字节的payload。

测试用例2:长度126到65535之间

例如,长度126,opcode为BINARY(2)。第一个字节是0x82,第二个字节是0x7E(126),后面两个字节是0x007E(126的大端),然后payload。

测试用例3:长度大于等于65536

例如,长度65536,opcode为PING(9)。第一个字节0x89,第二个字节0x7F(127)。接下来的四个字节为0,然后是四个字节的0x00010000(65536)。然后payload开始于第11字节。

另外,需要测试边缘情况,如长度125、126、65535、65536等。

测试时,可以编写测试函数,调用encodeMessage,然后检查返回的Buffer的各部分是否符合预期。

例如,对于长度125的payload:

const payload = Buffer.alloc(125, 0xAA); // 填充125个0xAA的Buffer

const buf = encodeMessage(opcodes.TEXT, payload);

// 检查buf的长度是否为125 + 2 = 127

// 第一个字节是否为0x81

// 第二个字节是否为125

// payload是否从第3字节开始正确复制

对于长度126的情况:

const payload = Buffer.alloc(126);

const buf = encodeMessage(opcodes.BINARY, payload);

// buf长度应为126 + 2 + 2 = 130

// 第二个字节是126(0x7E)

// 接下来两个字节是0x007E(大端)

对于大长度:

const payload = Buffer.alloc(70000); // 假设足够大

const buf = encodeMessage(opcodes.PONG, payload);

// 检查第二个字节是否为127(0x7F)

// 接下来的四个字节是0,然后四个字节是70000的大端表示

此外,还需要测试当payload长度为0时的情况,比如空Buffer是否处理正确。

可能的错误点:

  1. 当使用8字节长度时,是否应该写入64位长度而不是分两次32位?例如,对于非常大的长度(超过2^32),前32位可能不为零,但当前代码将前32位设置为0,这可能有问题。例如,如果payload长度是0x100000000(超过32位),则当前代码会错误地将其表示为0x00000000 00000001,而正确应该是高位32位为0x1,低位为0。但代码中因为使用length参数是payload.length,这在JavaScript中是一个number类型,当超过2^53时精度会丢失。所以实际应用中可能无法处理超过4GB的payload,但代码可能在这种情况下出错。不过对于一般测试,可能不需要考虑这么大的数据。

另一个可能的错误是在处理大端序时,是否正确分割了长度的高位和低位。例如,当长度为65536(0x00010000),用writeUInt32BE(0,2)和writeUInt32BE(length,6),那么第2到5字节是0,第6到9字节是0x00010000,这样整个长度是0x0000000000010000,即65536,是正确的。

此外,需要确认mask位是否为0,即第二个字节的最高位是0。例如,当b2被设置为length或126或127时,因为b2初始为0,且没有设置mask位(0x80),所以是正确的。例如,当payload长度是5,b2是5,即0x05,最高位是0,正确。

综上,测试用例应覆盖所有三个分支,并验证头部各字段以及payload的正确性。对于每个分支,可以编写多个测试用例,使用不同的长度和opcode,然后检查生成的Buffer是否符合WebSocket数据帧的规范。

要测试 encodeMessage 函数,需覆盖不同长度的 payload 和操作码,验证生成的 Buffer 是否符合 WebSocket 协议。以下是测试方法及逐行代码说明:

测试方法
测试用例划分:

短消息(长度 < 126)

中等消息(126 ≤ 长度 ≤ 65535)

长消息(长度 > 65535)

验证内容:

头部字段:FIN 位、操作码、Mask 位、长度字段。

Payload 复制:数据是否正确复制到 Buffer 中。

长度编码:长度是否按协议正确编码(1/2/8 字节)。

测试工具:

使用 Node.js 的 assert 模块或测试框架(如 Mocha)。

关键验证点

FIN 位和操作码:

第一个字节高 4 位为 0x80(FIN=1),低 4 位为操作码(如 1 表示 TEXT)。

Mask 位和长度字段:

第二个字节高 1 位为 0(Mask=0),低 7 位为长度或标识符(126/127)。

长度编码:

短消息:直接写入长度(1 字节)。

中等消息:写入 126 + 2 字节大端长度。

长消息:写入 127 + 8 字节大端长度(高 32 位为 0)。

Payload 复制:

数据从正确位置开始复制(如短消息从第 3 字节开始)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值