torutkのブログ

ソフトウェア・エンジニアのブログ

バイナリデータを扱う(bit操作を含む)ライブラリPreon

ネットワークで取り扱うデータ形式には、バイナリ形式のものが多く、さらにビット単位に割り付けられているものがあります。
例えば、時刻同期でおなじみのNTPでは、RFC2030で規定されているフォーマットがあります。
先頭部分を抜粋したのが次です。

                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          Root Delay                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

例えば先頭のLIは2ビットで次の意味になります。

ビット値 数値 内容
00 0 警告なし
01 1 最後の1分が61秒
10 2 最後の1分が59秒
11 3 警告状態(時計が同期していない)

もし、Javaでbyte[]からこのLI値を取り出すとするなら、次のようなコードになるでしょう。

byte[] message = ...
int leapIndicator = (message[0] & 0xc0) >> 6;
int versionNumber = (message[0] & 0x38) >> 3;
int stratum = message[1] & 0xff;

こんなのがたくさん書かないとしたら、どこが間違っているか分かりにくいしテストも大変だし、かなり苦痛なプログラミングが待っています。

そこで、バイト列であるデータの任意の場所からデータを切り出すライブラリが欲しくなります。

java.nio.ByteBufferがこの目的に比較的近いのですが、任意の場所(バイト境界ではなく任意のビット位置)から、任意のビット幅を切り出すことができません。結局バイトを取り出してビット操作をする必要があります。

ByteBufferをラップしてbit切り出しできるように使用かと考え、BitBufferを作ろうとしましたが、世の中には既に同じことを解決しているかもと探してみたら、Preonなるライブラリが見つかりました。
Preonは、bit表現のエンコード/デコードを行うオープンソースのライブラリです。

Preon

sourceforgecodehausなどを転々として現在はgithubソースコードを管理しています。
https://github.com/preon/preon

Preonのプレゼンテーション資料(OOPSLA 2009で作者のSpringer氏が発表)
http://www.slideshare.net/springerw/oopsla-talk-on-preon

この資料によると、アノテーションを用いてバイナリデータの構造を表現するクラスを作成します。

class Color {
  @Bound int red;
  @Bound int green;
  @Bound int red;
}

これは、32bit符号付き整数が3つ並んだバイナリデータを表現します。

                           1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          red                                  |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          green                                |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          red                                  |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

ファイルから読み込みデコードする例が資料にあります。(すこし修正しました。)

  File file = new File...
  Codec<Color> codec = Codecs.create(Color.class);
  Color color = Codecs.decode(codec, file);
  :

ビット幅の定義もできるので、最初にあげたNTPの例をアノテーションで定義すると

  class NtpMessage {
    @BoundNumber(size = "2") int leapIndicator;
    @BoundNumber(size = "3") int versionNumber;
    @BoundNumber(size = "3") int mode;
    @BoundNumber(size = "8") int stratum;
    @Bound byte poll;
    @Bound byte precision;
    @Bound int rootDelay;
      :
  }

となるかと思います。

可変長リストの定義や、値によってフィールドの有無が決まるなどのよくあるフォーマットにも対応しています。入れ子構造もできるので、かなり広範囲なフォーマットに対応できそうです。

アノテーションをうまく活用したAPIの設計例としても興味深いものがあります。

ライセンスは、GPLv2 with Classpath Exception なので、Preonを利用するアプリケーションはGPLにする必要はなくてすみます。