Goで固定長フォーマットを扱う

必要に迫られて、Goでバイナリの読み書きをしているのですが、encoding/binaryに関する解説が少ない気がしたのでまとめます。

## 固定長フォーマットとは

なんかこう、「先頭から 4byte, 2byte, 10byteという風に区切って、最初の4byteがA, 次の2byteはビッグエンディアンでuint32扱いでB, 次の10byteは文字列…」みたいに、一切の説明が省かれたフォーマットのことを指して発言しています。

当然、元データをparseして、構造体とかに変換してからプログラムで扱いたくなると思います。ただの`[]byte`のまま扱うとか地獄すぎですよね。

```go
type Foo struct{
  A [4]byte
  B uint32
  C uint16
  D uint16
}

func ParseBinary(in []byte) *Foo {
  // ...
}
```

```go
// 実行イメージ
func TestParseBinary(t *testing.T) {
  b := []byte{
    0x12, 0x34, 0x56, 0x78, // この辺はAの領域
    0x12, 0x34, 0x56, 0x78, // この辺はBの領域
    0x12, 0x34, // ここはCの領域
    0x56, 0x78, // ここはDの領域
  }

  // なんか関数を通すと
  got := ParseBinary(b)

  // 上から順番に、いい感じにぶった切ってFoo構造体に詰めてくれたらいいのに！
  want := &Foo{
    A: [...]byte{0x12, 0x34, 0x56, 0x78},
    B: 0x12345678,
    C: 0x1234,
    D: 0x5678,
  }

  if !reflect.DeepEqual(got, want) {
    t.Fatalf("got: %v, want: %v", got, want)
  }
}
```

## 結論から言うと

[encoding/binary](https://golang.org/pkg/encoding/binary)がとても便利です。

## []byteを読んでは詰める

binary.Readを使うと簡単に詰められます。

```go
func Read(r io.Reader, order ByteOrder, data interface{}) error
```

単純な使い方としては、uint32とか、uint64とかに詰めることです。

```go
package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

func main() {
	var i uint64
	buf := bytes.NewReader([]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x90})
	if err := binary.Read(buf, binary.BigEndian, &i); err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	fmt.Printf("0x%x\n", i)// 0x1234567812345678
}
```

intやfloatなどの数値系は、第二引数に `binary.BigEndian` か `binary.LittleEndian` を指定することで、エンディアンを指示できます。この例だと、uint64とは8byteなので、先頭から8byte分だけ読み取ってツメツメしています。(9バイト目の `0x90` は読み込まれていない)
もちろん `binary.LittleEndian` を指定すると、ひっくり返って `0x7856341278563412` になります。

binary.Readのすごいのは、structを渡すと、上から順番に詰めるというような芸当ができることです。
ツメツメするためにはそれぞれのフィールドに長さが設定されている必要があります。byteの配列を使ってフィールドを宣言しましょう。

ちょろっと書き換えて↓みたいにすると、

```go
type Foo struct{
  A [4]byte
  B [4]byte
  C uint8
}

func main() {
	var i Foo
	buf := bytes.NewReader([]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x90})
	if err := binary.Read(buf, binary.BigEndian, &i); err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	fmt.Printf("%x\n", i) // {12345678 12345678 90}
}
```

ちゃんと構造体のフィールドの定義順にツメツメしてくれてます！ 便利！

### スライスは使えないの？

ちなみにこんな風に、配列でなく`[]byte`みたいにスライスにはできないのか？と思うかもしれません。

```go
type Foo struct{
  A []byte
  B []byte
  C uint8
}
```

こちらはうまく動きません。

単体のスライス、 `[]byte` の場合は、makeで初期化しておくと動きました。capではなくlenを見ており、 `make([]byte, 9)` とかは大丈夫ですが、 `make([]byte, 0, 9)` みたいなのは長さ0なので何も読み込んでくれません。


```go
//これなら動く
	i := make([]byte, 9)
	buf := bytes.NewReader([]byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x90})
	if err := binary.Read(buf, binary.BigEndian, &i); err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	fmt.Printf("%x\n", i) // 123456781234567890
```

ドキュメントによると、以下の2つだけが許可されているとのこと。

> Data must be a pointer to a fixed-size value or a slice of fixed-size values.

* 固定長のvalueへのポインタ
* 固定長のvalueのスライス

なのでフィールドが全部固定長でできているstructならどんな構造でもうまく取り扱ってくれます。
スライスのスライスや、スライスを含むstructは駄目ってことですね。

## 固定長フォーマットで構造体を書き出す

ご想像の通り、Readがあるなら[Write](https://golang.org/pkg/encoding/binary/#Write)もあります。

```go
func Write(w io.Writer, order ByteOrder, data interface{}) error
```

さっきとは逆回しにFoo構造体を渡すと、io.Writerに追記していってくれます。これも便利。


## まとめ

encoding/binary便利です。

ちなみに、encoding一族に位置しているパッケージではありますが、趣旨がgoのネイティブの変数との相互変換なので、encoding/json や encoding/xml などとはインターフェースの流儀が異なります。 Marshal/Unmarshalなどもありません。