バイナリファイルパーサジェネレータが難しい理由
一つの定義文で、読み込みと書き込みの二つのプログラムを作ってほしい。
僕の修士論文のテーマはこれで、ライブラリ(ext packet)としてそのうちmoshに入る(higepon先生に却下されなければ)。
...というのを置いておくとすると、hachoirとかbinspect(や、Preccsやbinpacのようなプロトコル記述言語)がやっているように、ホスト言語 + 制限された構造体パーサ という組み合わせにせざるを得ない。
ASN.1 + BERのような組み合わせは既存のプロトコル(やファイル形式)が書けないという問題が有る。
(以下の要約 : 政治的に難しい)
(ext packet)の動作
packetライブラリは名前から想像出来るとおり、元々はネットワークプロトコルのパケットを生成するために制作された。
端的に言えばS式と構造体を相互に変換するためのライブラリとなっている(http://d.hatena.ne.jp/mjt/20091015/p1 のcompose)。
このpacketライブラリのための仕様記述言語(yuniScheme)をデザインするのが地味に難しい問題となる。
a. シンボル辞書を記述できる必要がある
yuniでは、SCSIの命令コード辞書を次のように記述できる。
(define-packet-format Command-Descriptor-Block (Operation-code u8) (/ /content)) (define-packet-format op/TEST-UNIT-READY (Reserved u32) (Control u8)) (define-alias op Command-Descriptor-Block/Operation-code) (define-dict op (0 TEST-UNIT-READY) (1 REZERO-UNIT) (3 REQUEST-SENSE) (4 FORMAT-UNIT) ...
(yuniはシンボル中のスラッシュを特別扱いし、シンボルは階層構造を作る)
この記述により、この辞書のシンボルを含む構造体がCommand-Descriptor-Block(CDB)であることが推論されるので、次のようなS式を、Command-Descriptor-BlockのOperation-Codeに0(= TEST-UNIT-READY)が入ったパケットとして構成できる。
(TEST-UNIT-READY :Control 0) => #vu8(0 0 0 0 0 0)
Hachoir等はこれを仕様として持っているわけではないので、無から構造体を生成する上手い方法は無い(仕様記述のエラーを検出できない)。
殆どのファイルやパケットは、少なくとも前から順番に読み取ることで曖昧でなくパース出来るように設計されている。
例えば、上のパケットは次のようにデコードされることも考えられるが、"未パースのデータを残さない"という単純なルールで大抵のプロトコルは意味のある形にデコードされる。
(Command-Descriptor-Block :Operation-code 0 :/content #vu8(0 0 0 0 0))
b. "正しい構造"を維持する必要がある
1カ所を変更すると、一貫性を保つために他の部分を修正しなければならないケースが発生する。
例えば、良くあるTLV構造で、Value(値)を修正した場合は"Valueの長さ"(Length)も修正しなければならない。
yuniは、"(チェックサムや長さのような、)生成的な値"と"(ユーザが興味を持つであろう)実際の値"を分け、"生成的な値"はS式に現れないように工夫している(不正な値を明示的に与えることも出来るが何か間違ったことが起こる)。