项目:创建二进制消息

使用显式字节排序和 ULEB128 并拒绝短输入。

Wave Foundation

消息格式

前 2 个字节存储类型号 big-endian u16,然后是值 ULEB128 u64。如果将结构内存按原样写入文件,它将受到填充和字节顺序的影响,因此请按字段对其进行编码。

将其保存为main.wave并运行。

import("std::bytes::types")::{
    ByteReader, ByteWriter
};
import("std::bytes::cursor")::{
    bytes_reader, bytes_writer, bytes_writer_write_be_u16, bytes_reader_read_be_u16
};
import("std::bytes::leb128")::{
    bytes_writer_write_uleb128_u64, bytes_reader_read_uleb128_u64
};

fun main() -> i32 {
    var data: array<u8, 12>;
    var writer: ByteWriter = bytes_writer(&data[0], 12);
    if (bytes_writer_write_be_u16(&writer, 7) < 0) {
        return 1;
    }
    if (bytes_writer_write_uleb128_u64(&writer, 300) < 0) {
        return 2;
    }

    var reader: ByteReader = bytes_reader(&data[0], writer.position);
    var kind: u16 = 0;
    var value: u64 = 0;
    if (bytes_reader_read_be_u16(&reader, &kind) < 0) {
        return 3;
    }
    if (bytes_reader_read_uleb128_u64(&reader, &value) < 0) {
        return 4;
    }

    println("kind={} value={} bytes={}", kind, value, reader.position);
    var short: ByteReader = bytes_reader(&data[0], 1);
    kind = 99;
    if (bytes_reader_read_be_u16(&short, &kind) >= 0) {
        return 5;
    }

    println("preserved={} {}", short.position, kind);
    return 0;
}

执行结果:

kind=7 value=300 bytes=4
preserved=0 99

要检查什么

在reader中,我们传递实际写入的长度,而不是数组的总容量12。这是为了避免读取未初始化的尾随字节作为输入。即使短输入读取失败,也会保持position=0 和kind=99。

扩展练习和评论

如果格式不允许末尾有多余字节,请在解析完成后检查reader.position == reader.len。添加长度字段时,请确保其不大于输入的剩余字节,并且长度+offset的计算不超出范围。

参见bytes

查看实际字节数

类型编号 7 是 big-endian u16,因此是 00 07。值 300 变为 ULEB128 到 AC 02。整个消息由以下四个字节组成:

00 07 AC 02
───── ─────
종류  값

ULEB128 每个字节使用低 7 位作为值,如果高位为 1,则表示接下来是下一个字节。 300的低7位是44,余数是2。第一个字节是44加上128个连续标记,或者172,或者0xAC。最后一个字节0x02没有连续标记。

容量和使用时间

u16使用2个字节,u64的ULEB128最多使用10个字节,因此一个12字节的数组可以存储这两个字段。值 300 仅使用 2 个字节,使得实际消息为 4 个字节。当发送到文件或套接字时,您发送字节writer.position而不是整个数组。

reader中的position是当前读取的位置。读取类型后变为2,读取值后变为4。为了在发生错误时继续处理下一条消息,必须知道失败消息的边界。消息恢复策略不会仅仅因为读取函数保留位置而自动建立。

边界值练习

将值更改为 0、127、128、16383、16384 以确定编码长度。当从127变为128时,ULEB128的长度从1增加到2,从16383变为16384时,长度从2增加到3。计算总长度,包括类型字段的2个字节。

最后一个字节被截断的消息也会被检查。如果原始消息长度为 4,我们将长度 3 传递给reader。读取了类型字段,但读取值一定会失败,因为ULEB128的最后一个字节丢失了。此时,检查位置2和读取ULEB128之前的输出值是否保持。