# 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)