为控制解包与打包过程,需要编写 数据布局规范(data layout specification),也称为 Bindat 类型表达式。它可以是 基础类型(base type),也可以是由多个字段组成的 复合类型(composite type),规范会控制每个待处理字段的长度,以及打包或解包的方式。我们通常将 bindat 类型值保存在名称以 -bindat-spec 结尾的变量中;这类名称会被自动识别为有风险(see 文件局部变量)。
根据 Bindat 类型 表达式 type 创建一个 Bindat 类型 值 对象。
字段的 类型(type) 描述了该字段所代表对象的大小(以字节为单位),对于多字节字段,还描述了字节在字段内部的排列顺序。两种可用的字节序分别是 大端序(big endian)(也称为“网络字节序”)和
小端序(little endian)。例如,数值 #x23cd(十进制 9165)以大端序存储时为两个字节 #x23 #xcd;
以小端序存储时则为 #xcd #x23。以下是可用的类型值:
u8byte无符号字节,长度为 1。
uint bitlen &optional le采用网络字节序(大端序)的无符号整数,长度为 bitlen 位。
bitlen 必须是 8 的倍数。
如果 le 非 nil,则使用小端序。
sint bitlen le采用网络字节序(大端序)的有符号整数,长度为 bitlen 位。
bitlen 必须是 8 的倍数。
如果 le 非 nil,则使用小端序。
str len长度为 len 字节的单字节字符串(see 文本表示方式)。
打包时,会将输入字符串的前 len 个字节复制到打包输出中。如果输入字符串长度小于 len,
剩余字节会填充为空字节(0),除非向 bindat-pack 提供了预分配的字符串,此时剩余字节保持不变。如果输入字符串是多字节且仅包含 ASCII 和
eight-bit 字符,会在打包前转换为单字节;其他多字节字符串会报错。解包时,
打包输入中的所有空字节都会出现在解包输出中。
strz &optional len如果未提供 len,则表示一个长度可变、以空字节结尾的单字节字符串(see 文本表示方式)。
打包为 strz 时,会将整个输入字符串复制到打包输出,随后追加一个空字节(0)。(如果为打包到
strz 提供了预分配字符串,该字符串应预留足够空间存放追加的空字节,see 字节解包与打包函数)。打包输出的长度
为输入字符串长度加一(用于存放结束空字节)。输入字符串不得包含任何空字节。
如果输入字符串是多字节且仅包含 ASCII 和 eight-bit 字符,会在打包前转换为单字节;
其他多字节字符串会报错。对 strz 进行解包时,
输出字符串会包含从起始到结束空字节(不含该空字节)的所有字节。
如果提供了 len,strz 的行为与 str 基本相同,但有以下几点区别:
Caution: 除非输入字符串长度小于 len 字节,或在前 len 字节内包含空字节,否则打包输出不会以空字节结尾。
vec len [type]包含 len 个元素的向量。元素的类型由 type 指定,默认为字节。type 可以是任意 Bindat 类型表达式。
repeat len [type]与 vec 类似,但它解包后为列表、打包时也从列表读取,而 vec 解包后为向量。
bits len由 len 字节中值为 1 的位构成的列表。字节按大端序读取,
位编号从 8 * len − 1 开始,到 0 结束。例如:
bits 2 会将 #x28 #x1c 解包为 (2 3 4 11 13),
将 #x1c #x28 解包为 (3 5 10 11 12)。
fill lenlen 个仅用作填充的字节。打包时,这些字节保持不变,通常为 0。
解包时,该类型直接返回 nil。
align len与 fill 相同,区别在于填充的字节数为跳转到 len 整数倍位置所需的字节数。
type exp允许间接引用一个类型:exp 是一个 Lisp 表达式,其返回值为一个 Bindat 类型 值。
unit exp一个极简类型,不占用任何存储空间。exp 描述对该字段执行 “解包(unpack)” 时返回的值。
struct fields...由多个字段组成的复合类型。每个字段的格式为
(name type),其中 type 可以是任意 Bindat
类型表达式。如果某个字段的值无需命名(常见于 align 和 fill 字段),name 可以设为 _。
当上下文明确表明这是 Bindat 类型表达式时,可以省略符号 struct。
在上述类型中,len 和 bitlen 以整数形式指定字段的字节(或位)长度。 如果某个字段的长度不固定,通常会依赖于前面字段的值。因此,长度 len 不必是常量, 可以是任意 Lisp 表达式,并且可以通过字段名引用前面字段的值。
例如,描述“首字节给出后续 16 位整数向量长度”的数据布局规范可以写成:
(bindat-type (len u8) (payload vec (1+ len) uint 16))