在 javascript/Nodejs 中从未知大小的字节流解码前置的 VarInt

Decode a prepended VarInt from a byte stream of unknown size in javascript/Nodejs

我正在为我编写一个小型实用程序库,以在节点上的 js 中请求给定 minecraft 主机的服务器状态。我正在使用此处概述的服务器列表 Ping 协议 (https://wiki.vg/Server_List_Ping) 并且它大部分都按预期工作,尽管在使用不受支持的数据类型 (VarInt) 时遇到了很大的麻烦并且不得不在互联网上搜索以查找一种将 js nums 转换为 VarInts 以制作必要的数据包缓冲区的方法:

function toVarIntBuffer(integer) {
    let buffer = Buffer.alloc(0);
    while (true) {
        let tmp = integer & 0b01111111;
        integer >>>= 7;
        if (integer != 0) {
            tmp |= 0b10000000;
        }
        buffer = Buffer.concat([buffer, Buffer.from([tmp])]);
        if (integer <= 0) break;
    }
    return buffer;
}

现在我可以通过发送握手数据包然后发送查询数据包来请求服务器状态,并确实收到一个 JSON 响应,响应长度作为 VarInt 前置。

但是问题就在这里,我根本不知道如何从 JSON 响应的开头安全地识别 VarInt(因为它可以是最多 5 个字节的任何地方)并将其解码回可读 num 这样我就可以获得响应字节流的正确长度。

[...] as with all strings this is prefixed by its length as a VarInt

(来自协议文档)

我目前的超级 hacky 解决方法是将块连接为 String,直到连接的字符串包含相同数量的 '{'s'}'s(意味着完整的 json object) 并在第一个 '{' 处对 json 响应进行切片,然后再对其进行解析。

然而,我对这种解决问题的方式非常不满意,这种方法很老套、低效、不优雅,而且可能不可靠,我宁愿解码 JSON 响应前面的 VarInt 以获得合适的长度进行比较反对。

我不知道这个协议,但是protobuf中的VarInt是用MSB位编码的:

Each byte in a varint, except the last byte, has the most significant bit (msb) set – this indicates that there are further bytes to come. The lower 7 bits of each byte are used to store the two's complement representation of the number in groups of 7 bits, least significant group first.

注意:评论太长,所以张贴作为答案。

更新:我浏览了一下你给的URL,确实是ProtoBuf VarInt。它也被描述为 pseudo-code:

https://wiki.vg/Protocol#VarInt_and_VarLong

VarInt and VarLong Variable-length format such that smaller numbers use fewer bytes. These are very similar to Protocol Buffer Varints: the 7 least significant bits are used to encode the value and the most significant bit indicates whether there's another byte after it for the next part of the number. The least significant group is written first, followed by each of the more significant groups; thus, VarInts are effectively little endian (however, groups are 7 bits, not 8).

VarInts are never longer than 5 bytes, and VarLongs are never longer than 10 bytes.

Pseudocode to read and write VarInts and VarLongs:

感谢 @thst 向我指出的参考文献 material,我能够拼凑出一种在 javascript.

中读取 VarInt 的工作方式
function readVarInt(buffer) {
    let value = 0;
    let length = 0;
    let currentByte;

    while (true) {
        currentByte = buffer[length];
        value |= (currentByte & 0x7F) << (length * 7);
        length += 1;
        if (length > 5) {
            throw new Error('VarInt exceeds allowed bounds.');
        }
        if ((currentByte & 0x80) != 0x80) break;
    }
    return value;
}

buffer 必须是以 VarInt 开头的字节流,最好使用 std Buffer class.