41.20.1 描述数据布局

为控制解包与打包过程,需要编写 数据布局规范(data layout specification),也称为 Bindat 类型表达式。它可以是 基础类型(base type),也可以是由多个字段组成的 复合类型(composite type),规范会控制每个待处理字段的长度,以及打包或解包的方式。我们通常将 bindat 类型值保存在名称以 -bindat-spec 结尾的变量中;这类名称会被自动识别为有风险(see 文件局部变量)。

Macro: bindat-type &rest type

根据 Bindat 类型 表达式 type 创建一个 Bindat 类型 对象。

字段的 类型(type) 描述了该字段所代表对象的大小(以字节为单位),对于多字节字段,还描述了字节在字段内部的排列顺序。两种可用的字节序分别是 大端序(big endian)(也称为“网络字节序”)和 小端序(little endian)。例如,数值 #x23cd(十进制 9165)以大端序存储时为两个字节 #x23 #xcd; 以小端序存储时则为 #xcd #x23。以下是可用的类型值:

u8
byte

无符号字节,长度为 1。

uint bitlen &optional le

采用网络字节序(大端序)的无符号整数,长度为 bitlen 位。 bitlen 必须是 8 的倍数。 如果 lenil,则使用小端序。

sint bitlen le

采用网络字节序(大端序)的有符号整数,长度为 bitlen 位。 bitlen 必须是 8 的倍数。 如果 lenil,则使用小端序。

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 进行解包时, 输出字符串会包含从起始到结束空字节(不含该空字节)的所有字节。

如果提供了 lenstrz 的行为与 str 基本相同,但有以下几点区别:

  • 打包时,如果输入字符串的字符数小于 len,会在打包后的输入字符串后写入一个空字节结束符。
  • 解包时,打包字符串中遇到的第一个空字节会被视为结束字节,该字节及其后续所有字节均不纳入解包结果。

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 len

len 个仅用作填充的字节。打包时,这些字节保持不变,通常为 0。 解包时,该类型直接返回 nil

align len

fill 相同,区别在于填充的字节数为跳转到 len 整数倍位置所需的字节数。

type exp

允许间接引用一个类型:exp 是一个 Lisp 表达式,其返回值为一个 Bindat 类型

unit exp

一个极简类型,不占用任何存储空间。exp 描述对该字段执行 “解包(unpack)” 时返回的值。

struct fields...

由多个字段组成的复合类型。每个字段的格式为 (name type),其中 type 可以是任意 Bindat 类型表达式。如果某个字段的值无需命名(常见于 alignfill 字段),name 可以设为 _。 当上下文明确表明这是 Bindat 类型表达式时,可以省略符号 struct

在上述类型中,lenbitlen 以整数形式指定字段的字节(或位)长度。 如果某个字段的长度不固定,通常会依赖于前面字段的值。因此,长度 len 不必是常量, 可以是任意 Lisp 表达式,并且可以通过字段名引用前面字段的值。

例如,描述“首字节给出后续 16 位整数向量长度”的数据布局规范可以写成:

(bindat-type
  (len      u8)
  (payload  vec (1+ len) uint 16))