---
# System prepended metadata

title: KaitaiStruct
tags: [summary, kaitai]

---

# KaitaiStruct

###### tags: `summary`, `kaitai`

[TOC]


---


## Введение
**[KaitaiStruct](http://kaitai.io/) (KS)** — декларативный язык описания бинарных форматов данных, основанный на [YAML](https://en.wikipedia.org/wiki/YAML). Язык активно поддерживается и развивается. Разрабатывается *open-source* инфраструктура языка, в которую входят:
* **компилятор**, транслирующий декларативное описание на языке KS в код парсера на целевом языке (`Python`, `C++`, `Java`, `JavaScript` и т.д.);
* **библиотека форматов**, описанных разработчиками (`gzip`, `ext2`, `pcap`, `elf` и т.д., более 100 форматов);
* **runtime-библиотеки**, необходимые для запуска сгенерированного компилятором кода;
* **визуализатор** результатов разбора (консольный);
* пользовательские руководства и прочее.

Все это делает KS весьма привлекательным инструментом.

| Достоинства | Недостатки |
|  --------   |  --------  |
| компактность и человекочитаемость языка <br>(YAML превосходит XML в удобстве) | отсутствие сущности состояния протокола |
| высокая выразительность языка <br>(все известные паттерны могут быть описаны на нем) |  |
| активная поддержка разработчиками |  |

Основной **пайплайн** работы с языком KS представляется следующим образом:
1. На языке KS описывается формат (описание сохраняется в `.ksy` файл)
2. Компилятор преобразует `.ksy` файл в код разборщика на целевом языке, получается библиотека/пакет/модуль для чтения и записи данного формата.
3. Библиотека импортируется в код проекта, в котором необходим функционал разбора, при помощи runtime-пакетов.

Таким образом, полностью пропадает необходимость самостоятельно писать программу для разбора формата.


## Установка и работа
Установка компилятора проводится по указаниям на главной станице:
```
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv 379CE192D401AB61

# Стабильная версия
echo "deb https://dl.bintray.com/kaitai-io/debian jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list
# Нестабильная версия (выбрать одно)
echo "deb https://dl.bintray.com/kaitai-io/debian_unstable jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list

sudo apt-get update
sudo apt-get install kaitai-struct-compiler
```
***Примечание:** возможно придется установить [Java Runtime Environment (JRE)](https://www.java.com/en/download/).*

Проверка установки:
```
ksc --version
```

Пример использования (ключ `-t` определяет целевой язык):
```
ksc -t python test.ksy
```
В результате в данной директории будет создан файл `myformat.py`, где *myformat* — имя формата, указанное в `.ksy` файле.

### Python-runtime
Для запуска сгенерированного кода необходим вспомогательный модуль *runtime*.

***Примечание:** рекомендуется использовать Python 3 с виртуальным окружением:*
```
python3 -m venv env
source env/bin/activate
```

При использовании стабильной версии компилятора, установить модуль можно через `pip`:
```
pip install kaitaistruct
```

Если вы устанавливали компилятор из нестабильной ветки, скорее всего актуальной версии модуля нет в PyPi, поэтому устанавливать модуль нужно из репозитория:
```
git clone git@github.com:kaitai-io/kaitai_struct_python_runtime.git
cd kaitai_struct_python_runtime
python setup.py install
```

Проверка установки:
```
python -c "import kaitaistruct"
```

***Примечание:** подобная установка runtime-пакетов потребуется и в случае использования других языков.*

### Использование сгенерированного разборщика

Результатом работы компилятора является код разборщика на целевом языке программирования. Для его запуска необходим *runtime*-модуль ([см. выше](#Python-runtime)).

Пусть мы скомпилировали в язык Python файл `test.ksy`, описывающий формат под названием `myformat` (это имя указывается в `meta`-секции `.ksy` файла). На выходе имеем модуль `myformat.py`.

Полученный парсер может быть вызван следующим образом:

```python
from myformat import Myformat

parsed_file = Myformat.from_file("/path/to/binary_file")
```
Параметром передается путь к файлу, который необходимо разобрать.

***Примечание:** Можно также использовать классовые методы `from_bytes` и `from_io`.*

### Визуализатор разбора
Консольный визуализатор разбора написан на Ruby и может быть установлен при помощи менеджера пакетов RubyGems:
```
gem install kaitai-struct-visualizer
```

Пример использования:
```
ksv sample.png png.ksy
```
Первым параметром передается файл для разбора, вторым — `.ksy` файл. Результат будет примерно следующим:

![kaitai-struct-visualizer](https://i.imgur.com/Nzsjsmg.png)


## Спецификация и особенности языка KaitaiStruct

*Ссылка на [полную спецификацию](http://doc.kaitai.io/ksy_reference.html) и на [руководство пользователя](http://doc.kaitai.io/user_guide.html)*

Все файлы .ksy используют язык разметки [YAML](https://en.wikipedia.org/wiki/YAML). Описание формата данных (в контексте Kaitai — *пользовательского типа*) разбивается на секции.

* Секция `meta` должна присутствовать в любом .ksy файле и определять в себе `id` (имя главного типа в этом файле). Позволяет импортировать другие .ksy файлы, задавать кодировку строк, порядок бит (little/big endian), специфицировать версию Kaitai.
    ```yaml
    meta:
        id: foo_arc
        title: Foo Archive
        ks-version: 9.9
        imports:
            - common/archive_header
            - common/compressed_file
        encoding: UTF-8
        endian: le
    ```

* Секция `seq` — непосредственно описание **атрибутов** данного типа. Атрибут `id` обязателен для всех типов и является "именем" данного типа или подтипа.

* **Базовые типы** (указываются в атрибуте `type` при определении типа):
  * Целые числа
  * Нецелые числа
  * Битовые поля
  * Строки
  * Массивы произвольных типов
*[полная таблица с описанием](http://doc.kaitai.io/userguide.html#basicdatatypes)*

* Секция `types` используется для определения **подтипов** (вспомогательных типов), представляет из себя отображение "имя" -\> "описание типа". Такие типы могут быть указаны в атрибуте `type` при определении типа.
    ```yaml
    seq:
        - id: track_title
          type: str_with_len
        - id: album_title
          type: str_with_len
        - id: artist_name
          type: str_with_len
    types:
        str_with_len:
            seq:
                - id: len
                  type: u4
                - id: value
                  type: str
                  encoding: UTF-8
                  size: len

    ```


* Вспомогательные секции: `enum` — именованные номера, `doc` — документация (при компиляции копируется в код\!)

* Секция `instances` — позволяет определять сегменты, не являющиеся частью последовательности. Например, сегменты с фиксированным отступом. Отличие от обычных типов — "ленивая" обработка: поле будет вычислено при вызове.
    *[подробнее](http://doc.kaitai.io)*

Другие особенности синтаксиса:

* **Декомпрессия/декодирование/т.п.** при помощи атрибута `process`. Доступные обработчики: `zlib`, `rol`/`ror`(побитовые сдвиги).
    ``` yaml
    seq:
      - id: buf1
        size: 0x1000
        process: zlib
    ```

* Передача в атрибут вычисляемых **выражений**, ссылающихся на значения других полей. Простой пример — передается длина сообщения, а затем само сообщение:
    ``` yaml
    seq:
      - id: my_len
        type: u4
      - id: my_str
        type: str
        size: my_len
        encoding: UTF-16LE
    ```
    *Выражения могут быть и более сложными, содержать арифметические операции.*

* **Разделители** (терминальные символы) для строк (*читай, пока не увидишь такой-то байт*):
    ``` yaml
    seq:
      - id: my_string
        type: str
        terminator: 0
    ```

* **Условные поля** (будут прочитаны только при определенном условии, определяемом выражением):
    ``` yaml
    seq:
      - id: has_crc32
        type: u1
      - id: crc32
        type: u4
        if: has_crc32 != 0
    ```
    *Второе число (называемое `crc32`) будет прочитано только если первое не равно нулю.*

* **[Повторения](http://doc.kaitai.io/user_guide.html#_repetitions)** данного поля. Определяется выражением, условием или определенное число раз (`repeat: eos`, `repeat: expr`, `repeat: 5`):
    ``` yaml
    seq:
      - id: num_floats
        type: u4
      - id: floats
        type: f8
        repeat: expr
        repeat-expr: num_floats
    ```
    *Сначала будет прочитано количество чисел `num_floats`, а затем именно это количество чисел.*

* **[Внешний тип](http://doc.kaitai.io/user_guide.html#opaque-types)**(*opaque type*) — имеется возможность перенести весь парсинг фрагмента потока в пользовательский код. Для этого необходимо реализовать интерфейс, соответствующий требованиям (конструктор принимает поток и т.д.). Компилировать .ksy файл необходимо с опцией `--opaque-types=true`, либо в секции `meta` добавить атрибут `ks-opaque-types: true`. Написав такое в описании типа:
    ```yaml
    seq:
        - id: doc
          type: custom_encrypted_object
    ```
    мы получим на выходе что-то вроде этого в коде (пример на Java):
    ```java
    public class DocContainer extends KaitaiStruct {
        // ...
        private void _read() {
            this.doc = new CustomEncryptedObject(this._io);
        }
    }
    ```
    Соответственно необходимо реализовать данный класс:
    ```java
    public class CustomEncryptedObject {
        // ...
        public CustomEncryptedObject(KaitaiStream io) {
            // ...
        }
    }
    ```

* **[Подпотоки](http://doc.kaitai.io/user_guide.html#_streams_and_substreams)** — в некоторых ситуациях (например, при появлении фиксированного размера `size` у типа), в основном потоке выделяется *подпоток*. В таком случае, такие атрибуты как сдвиг (pos) начинают отсчитываться от начала подпотока.


## Примеры

<details><summary>Фрагмент описания формата PNG</summary>

```yaml
meta:
  id: png
  title: PNG (Portable Network Graphics) file
  file-extension: png
  ks-version: 0.8
  endian: be
seq:
  - id: magic
    contents: [137, 80, 78, 71, 13, 10, 26, 10]
  - id: ihdr_len
    contents: [0, 0, 0, 13]
  - id: ihdr_type
    contents: "IHDR"
  - id: ihdr
    type: ihdr_chunk
  - id: ihdr_crc
    size: 4
  - id: chunks
    type: chunk
    repeat: until
    repeat-until: _.type == "IEND" or _io.eof
enums:
  color_type:
    0: greyscale
    2: truecolor
    3: indexed
    4: greyscale_alpha
    6: truecolor_alpha
types:
  chunk:
    seq:
      - id: len
        type: u4
      - id: type
        type: str
        size: 4
        encoding: UTF-8
      - id: body
        size: len
    # ...
```
</details>


## Архитектура компилятора

Генерация разборщика происходит в три базовых этапа:
1. Преобразование `.ksy` файла во **внутреннее представление** (синтаксический и семантический разбор)
2. **Предкомпиляция** — подготовительный этап (заготовка пространства имен, вычисление констант)
3. **Компиляция** внутрененго представления в код на целевом языке

### Внутреннее представление

Синтаксический разбор `.ksy` файла обеспечивается сторонней библиотекой [SnakeYAML](https://bitbucket.org/asomov/snakeyaml). На выходе получается базовое представление YAML файла в виде дерева со значениями в листовых вершинах.

Семантический разбор преобразует это дерево в аналогичное дерево, но теперь в вершинах стоят т.н. `Spec`-объекты (от *specification*).

`Spec`-объекты могут быть разных типов (`ClassSpec`, `DocSpec`, `AttrSpec`) и иметь различные атрибуты, представленные как константами, так и другими `Spec`-объектами. Таким образом и устанавливается соотвествие в дереве.

Например, базовый `Spec`-объект имеет тип `ClassSpec`, соответствующий корню дерева, и имеет следующие атрибуты:
```scala
ClassSpec(
  path: List[String],
  isTopLevel: Boolean,
  meta: MetaSpec,
  doc: DocSpec,
  params: List[ParamDefSpec],
  seq: List[AttrSpec],
  types: Map[String, ClassSpec],
  instances: Map[InstanceIdentifier, InstanceSpec],
  enums: Map[String, EnumSpec]
)
```
Таким образом, корневая вершина `ClassSpec` имеет дочерние вершины типа `MetaSpec`, `DocSpec`, набор вершин типа `AttrSpec` и т.д.

Составленное таким образом дерево является внутренним представлением полученного описания формата (`.ksy` файла).

После этого преобразования рекурсивно выполняются также *импорты* других `.ksy` файлов, если таковые указаны.

#### Пример
Рассмотрим пример примитивного `.ksy` файла:
```yaml
meta:
  id: myform
seq:
  - id: myattr
    type: u1
```
Его внутреннее представление можно вывести в таком виде:
```scala
ClassSpec(
    List(),
    true,
    MetaSpec(
        List(meta),
        false,
        Some(myform),
        None,
        None,
        false,
        None,
        List()
    ),
    DocSpec(
        None,
        List()
    ),
    List(),
    List(
        AttrSpec(
            List(seq, 0),
            NamedIdentifier(myattr),
            Int1Type(false),
            ConditionalSpec(None,NoRepeat),
            DocSpec(None,List())
        )
    ),
    Map(),
    Map(),
    Map()
)
```

### Предкомпиляция

На этом этапе полученные `Spec`-объекты валидируются и дополняются вспомогательными данными.

### Компиляция

На этом этапе происходит преобразование имеющейся структуры непосредственно в код на выбранном языке.
Список поддерживаемых на данный момент языков: `graphviz`, `csharp`, `rust`, `perl`, `java`, `go`, `cpp_stl`, `php`, `lua`, `python`, `html`, `ruby`, `construct`, `javascript`.

Подход к компиляции в "классические" языки, оперирующие сущностями классов (`Python`, `Java` и т.д.), обобщается, поскольку в таком случае возникает много общего в процессе генерации. Варьируются шаблоны классов, однако алгоритм по сути остается одним и тем же (исключение: `Go`).

Другие языки (`graphviz` и т.д.), имеют собственную реализацию.

*Описание архитектуры от разработчиков Kaitai: [ссылка](http://doc.kaitai.io/developers_intro.html)*

## Исходный код

Компилятор реализован на языке Scala. Здесь приводятся краткие инструкции по самостоятельному запуску этого кода.

Скачать исходный код всей разрабатываемой инфраструктуры можно из [GutHub репозитория](https://github.com/kaitai-io/kaitai_struct):
```
git clone --recursive https://github.com/kaitai-io/kaitai_struct.git
```

Для сборки проекта можно использовать интерактивный инструмент сборки [SBT](https://www.scala-sbt.org/). Для его установки необходимы JDK и JRE (рекомендуется версия >= 8), которые должны установиться автоматически вместе с ним.

Установка:
```
echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
sudo apt-get update
sudo apt-get install sbt
```

Перейти в папку проекта:
```
cd kaitai_struct/compiler
```

Сборка:
```
sbt compilerJVM/stage
```
***Примечание:** первый раз процесс может идти не быстро, SBT закачивает сторонние файлы*

Компилятор работает в консольном режиме, принимает два обязательных аргумента: целевой язык `lang` и .ksy файл `filepath`. Запуск:

```
sbt "compilerJVM/run --target <lang> <filepath>"
```

SBT имеет интерактивный режим, который позволяет более оперативно и удобно запускать код. Его запуск (в директории compiler):
```
sbt
```
Откроется консоль sbt. Пример дальнейшей работы:
```
sbt:root> compilerJVM/stage
sbt:root> compilerJVM/run --target <lang> <filepath>
```
Полезная опция: `~`
```
sbt:root> ~compilerJVM/run --target <lang> <filepath>
```
Знак `~` заставляет программу ждать изменений в коде и перезапускаться сразу при сохранении файлов кода.

*Памятка разработчику от разработчиков Kaitai: [ссылка](http://doc.kaitai.io/developers.html)*

## Ссылки

* [Сайт](http://kaitai.io/)
* [Сборник всех материалов](https://github.com/kaitai-io/awesome-kaitai)  о KaitaiStruct от разработчиков
* [Основной репозиторий Github](https://github.com/kaitai-io/kaitai_struct)
* [Спецификация языка](https://doc.kaitai.io/ksy_reference.html)
* [Руководство пользователя](http://doc.kaitai.io/user_guide.html)
* [База форматов](https://github.com/kaitai-io/kaitai_struct_formats) (или [на сайте](http://formats.kaitai.io/))
* Веб-инструменты: [Web IDE](https://ide.kaitai.io/) и [компилятор](http://kaitai.io/repl/index.html)
* [Python-обертка](https://github.com/KOLANICH/kaitaiStructCompile.py) компилятора (компиляция `.ksy` файлов в `.py` из Python)
