41.20.1 Описание структуры данных

Для управления распаковкой и упаковкой вы пишете спецификацию структуры данных, также называемую выражением типа Bindat. Это может быть базовый тип или составной тип, состоящий из нескольких полей, где спецификация контролирует длину каждого поля, которое будет обработано, а также то, как оно будет упаковано или распаковано. Обычно мы храним значения типа bindat в переменных, чьи имена оканчиваются на -bindat-spec; такое имя автоматически распознается как рискованное (см. Локальные переменные файла).

Макрос: bindat-type &rest type

Создает объект типа Bindat, соответствующий выражению типа Bindat type.

Тип поля описывает размер (в байтах) объекта, который представляет поле, а в случае многобайтовых полей — порядок байтов внутри поля. Два возможных порядка — это big endian (также известный как «порядок байтов сети») и little endian. Например, число #x23cd (десятичное 9165) в big endian будет двумя байтами #x23 #xcd; а в little endian — #xcd #x23. Вот возможные значения типов:

u8
byte

Беззнаковый байт, длина 1.

uint bitlen &optional le

Беззнаковое целое число в сетевом порядке байтов (big-endian) с bitlen битами. bitlen должно быть кратно 8. Если le не равно nil, используется порядок байтов little-endian.

sint bitlen le

Знаковое целое число в сетевом порядке байтов (big-endian) с bitlen битами. bitlen должно быть кратно 8. Если le не равно nil, используется порядок байтов little-endian.

str len

Однобайтовая строка (см. Представления текста) длиной len байт. При упаковке первые len байт входной строки копируются в упакованный вывод. Если входная строка короче, чем len, оставшиеся байты будут нулевыми (ноль), если только не была предоставлена предварительно выделенная строка для bindat-pack, в этом случае оставшиеся байты остаются неизменными. Если входная строка многобайтовая, но содержит только символы ASCII и eight-bit, она преобразуется в однобайтовую перед упаковкой; другие многобайтовые строки вызывают ошибку. При распаковке любые нулевые байты во входной упакованной строке появятся в распакованном выводе.

strz &optional len

Если len не предоставлен, это однобайтовая строка переменной длины, завершающаяся нулем (см. Представления текста). При упаковке в strz вся входная строка копируется в упакованный вывод, за которой следует нулевой (ноль) байт. (Если для упаковки в strz предоставлена предварительно выделенная строка, эта предварительно выделенная строка должна иметь достаточно места для дополнительного нулевого байта, добавляемого к содержимому выходной строки, см. Функции для распаковки и упаковки байтов). Длина упакованного вывода равна длине входной строки плюс один (для нулевого терминатора). Входная строка не должна содержать нулевых байтов. Если входная строка многобайтовая, но содержит только символы ASCII и eight-bit, она преобразуется в однобайтовую перед упаковкой; другие многобайтовые строки вызывают ошибку. При распаковке strz результирующая выходная строка будет содержать все байты до (но не включая) нулевого байта, который завершил входную строку.

Если len предоставлен, strz ведет себя так же, как str, но с парой отличий:

  • При упаковке после упакованной входной строки записывается нулевой терминатор, если количество символов во входной строке меньше, чем len.
  • При распаковке первый нулевой байт, встреченный в упакованной строке, интерпретируется как завершающий байт, и он, а также все последующие байты исключаются из результата распаковки.

Внимание: Упакованный вывод не будет иметь нулевого терминатора, если входная строка короче, чем len байт, или если она содержит нулевой байт в пределах первых len байт.

vec len [type]

Вектор из len элементов. Тип элементов задается type, по умолчанию — байты. type может быть любым выражением типа Bindat.

repeat len [type]

Подобно vec, но распаковывается и упаковывается в списки, тогда как vec распаковывается в векторы.

bits len

Список битов, установленных в 1 в len байтах. Байты берутся в порядке big-endian, а биты нумеруются, начиная с 8 * len − 1 и заканчивая нулем. Например: bits 2 распаковывает #x28 #x1c в (2 3 4 11 13) и #x1c #x28 в (3 5 10 11 12).

fill len

len байт, используемые просто как заполнитель. При упаковке эти байты остаются неизменными, что обычно означает, что они остаются нулями. При распаковке это просто возвращает nil.

align len

То же, что и fill, за исключением того, что количество байтов равно тому, что необходимо пропустить до следующего кратного len байтам.

type exp

Это позволяет косвенно ссылаться на тип: exp — это выражение Lisp, которое должно вернуть значение типа Bindat.

unit exp

Это тривиальный тип, который занимает 0 бит пространства. exp описывает значение, возвращаемое при попытке «распаковать» такое поле.

struct fields...

Составной тип, состоящий из нескольких полей. Каждое поле имеет вид (name type), где type может быть любым выражением типа Bindat. name может быть _, когда значение поля не заслуживает именования, как это часто бывает для полей align и fill. Когда контекст ясно указывает, что это выражение типа Bindat, символ struct можно опустить.

В вышеуказанных типах len и bitlen задаются как целое число, указывающее количество байтов (или битов) в поле. Когда длина поля не фиксирована, она обычно зависит от значения предыдущих полей. По этой причине длина len не обязательно должна быть константой, а может быть любым выражением Lisp и может ссылаться на значения предыдущих полей по их имени.

Например, спецификация структуры данных, где ведущий байт указывает размер последующего вектора 16-битных целых чисел, может выглядеть так:

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