# 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` файл. Результат будет примерно следующим:

## Спецификация и особенности языка 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)