Rust+nomでバイナリ(bmpファイル)を読もう

Rustでバイナリをパースする方法はいくつかありますが、nomでやってみたら以外と簡単だったので記事にします。

# nomとは

GitHub: https://github.com/Geal/nom

Rustのパーサコンビネータライブラリのデファクトスタンダード的な存在です。昔はマクロ中心の記述が中心で、マクロに慣れないと辛いところもありましたが、バージョン5からimpl Traitを活用した関数中心の記述に変更されました。マクロを使わないと書けないところも残っていたりしますが、かなり簡単にパーサコンビネータを使うことができます。文字列だけでなくバイナリのパーサを書くのにも使えます。

# 目標

バイナリを読む練習として、今回は仕様がかなり簡単な部類のバイナリファイルであるbmp(Windows Bitmap)ファイルを読むことを目標とします。フォーマットは[Wikipedia](https://ja.wikipedia.org/wiki/Windows_bitmap)を読めばわかります。bmpファイルの先頭に BITMAPFILEHEADER というファイルヘッダがあり、その後に情報ヘッダが続きます。情報ヘッダにはいくつかバージョンの違いがあるようですが、最初の40バイトは共通のようですので、そこまで読めたらあとの情報は切り捨てます。結論としては、ファイルを読み込んで次の`BitMap`構造体を作成することを目標とします。今回は簡単のため、インデックスカラーは扱わず、24ビットフルカラーか8ビットグレースケールのみ扱うこととします。

``` Rust
#[derive(Debug)]
struct BitMapFileHeader {
    /// ファイルサイズ
    file_size: u32,
    /// ファイル先頭からビットマップデータまでのオフセット
    offset: u32,
}

#[derive(Debug)]
struct BitMapInfoHeader {
    /// 横幅
    width: i32,
    /// 縦幅
    height: i32,
    /// 1ピクセルあたりのビット数
    bits_per_pixel: u16,
    /// 圧縮形式
    compression_method: u32,
    /// 画像データサイズ
    image_size: u32,
    /// 水平方向の解像度
    horizontal_resolution: i32,
    /// 垂直方向の解像度
    vertical_resolution: i32,
    /// カラーインデックスの数
    n_color_palette: u32,
    /// 使用されているカラーインデックスの数
    n_colors_used: u32,
}

struct BitMap {
    file_header: BitMapFileHeader,
    info_header: BitMapInfoHeader,
    /// ピクセルデータ
    pixels: Vec<u8>,
}
```

# 下準備

nomを使うために、次の依存関係をCargo.tomlに追記します。

```
[dependencies]
nom = "5"
```

`use`文を追加します。

``` Rust
use nom::bytes::complete::{tag, take};
use nom::number::complete::*;
use nom::IResult;
use nom::{error::ErrorKind, Err};
use std::io::Read;
use std::path::Path;
```

nomがパースの対象として扱える型は、今の所`&[u8]`か`&str`、つまりメモリ上にロードされたバイナリ列か文字列です。今回はbmpファイルを読み込むので、ファイルの中身をメモリ上にロードします。

``` Rust
fn read_file<P: AsRef<Path>>(file_path: P) -> Vec<u8> {
    let mut file = std::fs::File::open(file_path).expect("file open failed");
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).expect("file read failed");
    buf
}
```

# BITMAPFILEHEADERの読み込み

まずはファイルの先頭にあるBITMAPFILEHEADERを読み出すことを考えます。このヘッダの定義を抜き出してみると、次のようになります。

| 先頭からのオフセット | サイズ | 格納する情報 |
| --- | --- | --- |
| 0バイト | 2バイト | マジックナンバー 0x42, 0x4d |
| 2バイト | 4バイト | ファイルサイズ |
| 6バイト | 2バイト | 常に0  |
| 8バイト | 2バイト | 常に0  |
| 10バイト | 4バイト | ファイルヘッダの先頭アドレスからビットマップデータの先頭アドレスまでのオフセット（単位はバイト）


まず先頭は2バイトのマジックナンバー`0x42, 0x4d`なので、これに合致するかどうか判定します。そのために`nom::bytes::complete::tag`を使います。これの定義は次のようになります。

``` Rust
pub fn tag<'a, T: 'a, Input: 'a, Error: ParseError<Input>>(
    tag: T
) -> impl Fn(Input) -> IResult<Input, Input, Error> where
    Input: InputTake + Compare<T>,
    T: InputLength + Clone, 
```

長ったらしくてぱっと見ではよくわからなくなりますが、戻り値が`impl Fn`なので、関数として呼び出せることがわかります。そしてこの関数は、入力を受け取ってそれがtagに渡したバイナリ列と合致するか判定します。要するに次のように使います。ただし右辺に出てくる`input`は読み出したファイルの内容で、`&[u8]`型とします。

``` Rust
let (input, _) = tag(b"\x42\x4d")(input)?;
```

`tag(b"\x42\x4d")(input)`は、inputの先頭2バイトが`b"\x42\x4d"`と一致するか判定します。この結果は`Result`型であり、一致しない場合は`Err`となります。成功時の結果を得るためには`?`で取り出します。取り出されるのはタプルです。タプルの1番目は、入力を読み進めてて残った部分、今回はファイル先頭から2バイト進んだところを指す`&[u8]`となります。これを新しい`input`として定義します。タプルの2番目には、入力のうち``b"\x42\x4d"`と一致した部分のスライスです。これはもう必要ないので`_`に代入します。

ここで、再定義した`input`はファイルデータの3バイト目(オフセット2バイト)を指しています。ここからは4バイト整数のファイルサイズが格納されています。これを読み出すには、`nom::number::complete::le_u32`を用います。この関数は名前を見てわかる通り、リトルエンディアンの32bit符号なし整数を読み出します。これを使うと、ファイルサイズは次のように読み出せます。

``` Rust
let (input, file_size) = le_u32(input)?; // ファイルサイズ
```

戻り値のタプルのうち、1番目は先程と同様に、右辺で渡している`input`からさらに4バイト読み進めたのこりのスライスです。2番目は読み込んだ4バイトに入っていたデータをリトルエンディアンとして解釈して出力した`u32`の値です。これは必要な情報なので`file_size`と名前をつけておきます。

さて、ここまでで`tag`と`le_u32`というnomの関数を使ってきましたが、どちらもタプルを返しています [^1] 。このタプルの1番目は、入力を読み込んで使わなかった部分のスライスで、2番目は関数が読み込んだデータを解釈して生成した値であり、場合によって型はまちまちです。つまり、nomでパーサを書く場合、次のような関数を定義して使います。

``` Rust
fn my_parser(input: &[u8]) -> IResult<&[u8], MyData>;
```

この`my_parser`関数は、`input`の先頭部分を解釈し、`IResult`型を返します。`IResult`は次のように定義されており、中身は`Result`です。

``` Rust
type IResult<I, O, E = (I, ErrorKind)> = Result<(I, O), Err<E>>;
```

`Result`のうち`Ok`に入るのはタプルであり、タプルの中身は先程解説したのと同様、残りのインプットと、パーサが解釈した結果(`MyData`型)を返します。このような1部分を解釈するパーサ関数を組み合わせて全体のパーサを作成することがnomの基本方針です。

それではこの方針に基づき、bmpファイルの先頭を読み込み、`BitMapFileHeader`構造体を返す関数`read_bitmap_file_header()`を書いてみます。

``` Rust
fn read_bitmap_file_header(input: &[u8]) -> IResult<&[u8], BitMapFileHeader> {
    let (input, _) = tag(b"\x42\x4d")(input)?; // 先頭のマジックナンバー
    let (input, file_size) = le_u32(input)?; // ファイルサイズ
    let (input, _) = tag([0u8, 0])(input)?; // 0が入っている予約領域
    let (input, _) = tag([0u8, 0])(input)?; // 0が入っている予約領域
    let (input, offset) = le_u32(input)?; // 先頭から画像データdまでのオフセット
    Ok((input, BitMapFileHeader { file_size, offset }))
}
```

`tag`と`le_u32`を用いて、順番に定義通り読み出していくだけです。最後に集まった情報(`file_size`と`offset`)から`BitMapFileHeader`を生成し、読み残しの`input`と一緒にタプルにして返します。

この関数内では、`input`を読み進めるたびに再定義しています。別の名前にすることも可能です。ただしややこしくなるので同じ名前にするのがいいでしょう。

ところで、`?`を使っていることからわかるように、読み込みに失敗した関数は`Err`を返します。

# 情報ヘッダの読み込み

先述の通り、情報ヘッダには複数バージョンが存在しますが、40バイト読んであとは無視します。この場合、[BITMAPINFOHEADER](https://ja.wikipedia.org/wiki/Windows_bitmap#BITMAPINFOHEADER)の定義通りに読んでいきます。

``` Rust
fn read_bitmap_info_header(input: &[u8]) -> IResult<&[u8], BitMapInfoHeader> {
    let (input, _) = le_u32(input)?; // ヘッダのサイズ
    let (input, width) = le_i32(input)?; // 横幅
    let (input, height) = le_i32(input)?; // 縦幅
    let (input, _) = tag(&[1u8, 0])(input)?; // プレーン数 (常に1)
    let (input, bits_per_pixel) = le_u16(input)?; // 1ピクセルあたりのビット数
    let (input, compression_method) = le_u32(input)?; // 圧縮形式
    let (input, image_size) = le_u32(input)?; // 画像データサイズ
    let (input, horizontal_resolution) = le_i32(input)?; // 水平方向の解像度
    let (input, vertical_resolution) = le_i32(input)?; // 垂直方向の解像度
    let (input, n_color_palette) = le_u32(input)?; // カラーインデックスの数
    let (input, n_colors_used) = le_u32(input)?; // 使用するカラーインデックスの数
    Ok((
        input,
        BitMapInfoHeader {
            width,
            height,
            bits_per_pixel,
            compression_method,
            image_size,
            horizontal_resolution,
            vertical_resolution,
            n_color_palette,
            n_colors_used,
        },
    ))
}
```

ちょっと長くなりましたが、やってることは`read_bitmap_file_header()`と同じく、順番に読んでいき、結果と残りの入力を返しているだけです。

# ビットマップ全体の読み込み

ヘッダ部分を読むパーサができたので、ビットマップ全体を読む関数を作ります。

``` Rust
fn read_bmp(input: &[u8]) -> Result<BitMap, Err<(&[u8], ErrorKind)>> {
    let (input, file_header) = read_bitmap_file_header(input)?;
    let (input_pixels, input_info_header) = take(file_header.offset - 14)(input)?;
    let (_, info_header) = read_bitmap_info_header(input_info_header)?;
    let pixels = input_pixels.to_vec(); // ピクセルデータをそのままVec<u8>に格納

    Ok(BitMap {
        file_header,
        info_header,
        pixels,
    })
}
```

`file_header`を読んだ後、ピクセルデータが格納される領域までのオフセット`file_header.offset`に基づき、`input`を`read_bitmap_info_header()`に渡す`input_info_header`と、ピクセルデータを指す`input_pixels`に分けます。これには`nom::bytes::complete::take`を用います。`file_header.offset`はピクセルデータのファイル先頭からのオフセットですが、`input`はすでにファイルヘッダの分だけ読み進めているので、`take`に渡す値からその分を引いておきます。

今回はピクセル情報を生データそのまま`Vec`にしていますが、ピクセル情報を他の型へパースすることにもnomが使えるはずです。

# ビットマップを表示する

ビットマップが読み込めたか調べるため、ヘッダ情報とピクセルを表示する`BitMap::print`を実装します。各ピクセルの明度に基づいてターミナル上にAscii Art風の表示をさせます。

``` Rust
impl BitMap {
    fn print(&self) {
        println!("{:?}", self.file_header);
        println!("{:?}", self.info_header);

        let bytes_per_pixel = i32::from(self.info_header.bits_per_pixel) / 8;
        let w = self.info_header.width * bytes_per_pixel;
        let w = if w % 4 == 0 { w } else { w - w % 4 + 4 };
        for y in (0..self.info_header.height).rev() {
            for x in 0..self.info_header.width {
                let i = (y * w + x * bytes_per_pixel) as usize;
                let value = if bytes_per_pixel == 3 {
                    (self.pixels[i] as u32 + self.pixels[i + 1] as u32 + self.pixels[i + 2] as u32) / 3
                } else {
                    self.pixels[i] as u32
                };
                let c = if value < 64 {
                    '*'
                } else if value < 128 {
                    '+'
                } else if value < 192 {
                    '-'
                } else {
                    ' '
                };
                print!("{}", c);
            }
            println!();
        }
    }
}
```

# 実行する

`main`関数は次のように実装します。エラーが起きた場合は表示だけはするようにします。

``` Rust
fn main() {
    let data = read_file("sample.bmp"); // ファイルを読み込んでVec<u8>にする
    match read_bmp(&data) { // パースする
        Ok(bmp) => {
            bmp.print();
        }
        Err(err) => {
            println!("{:?}", err);
        }
    }
}
```

読み込む画像として、次のようなRustのロゴを小さくしたものを用意します。

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182229/7d8438b3-3455-3211-cf95-506714de0919.png)

これをWindows BitMapとしてsample.bmpに保存し、実行したところ以下のような出力となりました。

```
BitMapFileHeader { file_size: 1722, offset: 1146 }
BitMapInfoHeader { width: 24, height: 24, bits_per_pixel: 8, compression_method: 0, image_size: 576, horizontal_resolution: 11811, vertical_resolution: 11811, n_color_palette: 256, n_colors_used: 256 }
           -            
       - *+*++---       
     --**** +***+--     
     ***+-- ---***-     
   -**+    -    -***    
  -+*+            **--  
  +**+----------  -**-  
 -**************+  -*+- 
 +*+*+***********- ++*- 
-+- + ***-    ***+ - *- 
-**+- ***+   -***  -+*+ 
-**   **********-   -*+ 
+*+   **********+   -**-
 **   ***-   +***  -**+ 
+**---***+   -***+ +*** 
 +**********  ********  
 ***********  +*******- 
  +*+             -**-  
  ***++-        +-+**-  
   -*+ +          **    
   --*-*-     -++++-    
     *+**********+-     
       *+*****++-       
         + +---         
```

ヘッダ情報、ピクセルともに正しく読み込めていることが確認できました。

# おわりに

今回題材に取り上げたビットマップファイルでは、ヘッダを順番に読んでいくだけで、nomの各種コンビネータの力はあまり発揮されているように感じませんが、それでも自力でパースするよりはずっと楽なはずです。古い記事ではnomはマクロ中心と紹介されていたりしますが、バージョン5以降は関数中心になり、敷居も下がっているので試してみるのはいかがでしょうか。

[^1]: 正確には、`tag`はそういった機能をもつ`impl Fn`を返します。